feat(sdk): Push deep link routing for all platforms
- TypeScript: DeepLinkRouter with URL parsing and handler registration - Swift: BLDeepLinkRouter with iOS URL handling and Logger integration - Kotlin: DeepLinkRouter with Android Uri parsing and handler mapping - Common screen constants: broadcasts, surveys, settings, profile, etc.
This commit is contained in:
parent
6e0b6c33c9
commit
18dd263797
161
packages/broadcast-client/src/deep-link.ts
Normal file
161
packages/broadcast-client/src/deep-link.ts
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
/**
|
||||||
|
* Deep Link Router — TypeScript
|
||||||
|
* Handles routing from push notification deep links to app screens
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface DeepLinkRoute {
|
||||||
|
screen: string;
|
||||||
|
params?: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DeepLinkHandler = (route: DeepLinkRoute) => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deep Link Router class
|
||||||
|
*/
|
||||||
|
export class DeepLinkRouter {
|
||||||
|
private handlers = new Map<string, DeepLinkHandler>();
|
||||||
|
private fallbackHandler?: DeepLinkHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a handler for a specific screen
|
||||||
|
*/
|
||||||
|
register(screen: string, handler: DeepLinkHandler): void {
|
||||||
|
this.handlers.set(screen, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a fallback handler for unregistered screens
|
||||||
|
*/
|
||||||
|
setFallback(handler: DeepLinkHandler): void {
|
||||||
|
this.fallbackHandler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a deep link URL and extract route
|
||||||
|
*/
|
||||||
|
parseDeepLink(url: string): DeepLinkRoute | null {
|
||||||
|
try {
|
||||||
|
const urlObj = new URL(url);
|
||||||
|
|
||||||
|
// Handle app-specific URLs: myapp://screen/params
|
||||||
|
if (urlObj.protocol !== 'http:' && urlObj.protocol !== 'https:') {
|
||||||
|
const pathParts = urlObj.pathname.split('/').filter(Boolean);
|
||||||
|
const screen = pathParts[0] || 'home';
|
||||||
|
const params: Record<string, string> = {};
|
||||||
|
|
||||||
|
// Parse query params
|
||||||
|
urlObj.searchParams.forEach((value, key) => {
|
||||||
|
params[key] = value;
|
||||||
|
});
|
||||||
|
|
||||||
|
return { screen, params };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle web URLs with deep link params
|
||||||
|
const deepLinkParam = urlObj.searchParams.get('dl');
|
||||||
|
if (deepLinkParam) {
|
||||||
|
return this.parseDeepLink(deepLinkParam);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle path-based routing: /screen/params
|
||||||
|
const pathParts = urlObj.pathname.split('/').filter(Boolean);
|
||||||
|
if (pathParts.length > 0) {
|
||||||
|
const screen = pathParts[0];
|
||||||
|
const params: Record<string, string> = {};
|
||||||
|
|
||||||
|
urlObj.searchParams.forEach((value, key) => {
|
||||||
|
params[key] = value;
|
||||||
|
});
|
||||||
|
|
||||||
|
return { screen, params };
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a deep link route
|
||||||
|
*/
|
||||||
|
handle(route: DeepLinkRoute): boolean {
|
||||||
|
const handler = this.handlers.get(route.screen);
|
||||||
|
|
||||||
|
if (handler) {
|
||||||
|
handler(route);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.fallbackHandler) {
|
||||||
|
this.fallbackHandler(route);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.warn(`[DeepLink] No handler for screen: ${route.screen}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process a deep link URL end-to-end
|
||||||
|
*/
|
||||||
|
process(url: string): boolean {
|
||||||
|
const route = this.parseDeepLink(url);
|
||||||
|
if (!route) {
|
||||||
|
console.warn(`[DeepLink] Failed to parse: ${url}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this.handle(route);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a broadcast deep link URL
|
||||||
|
*/
|
||||||
|
export function createBroadcastDeepLink(
|
||||||
|
baseUrl: string,
|
||||||
|
screen: string,
|
||||||
|
params?: Record<string, string>,
|
||||||
|
broadcastId?: string
|
||||||
|
): string {
|
||||||
|
const url = new URL(baseUrl);
|
||||||
|
url.pathname = `/${screen}`;
|
||||||
|
|
||||||
|
if (params) {
|
||||||
|
Object.entries(params).forEach(([key, value]) => {
|
||||||
|
url.searchParams.set(key, value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (broadcastId) {
|
||||||
|
url.searchParams.set('broadcastId', broadcastId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return url.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common deep link screens for broadcast/survey flows
|
||||||
|
*/
|
||||||
|
export const DeepLinkScreens = {
|
||||||
|
// Broadcasts
|
||||||
|
BROADCAST_DETAIL: 'broadcast',
|
||||||
|
ANNOUNCEMENTS: 'announcements',
|
||||||
|
|
||||||
|
// Surveys
|
||||||
|
SURVEY: 'survey',
|
||||||
|
SURVEY_LIST: 'surveys',
|
||||||
|
|
||||||
|
// Product-specific (examples)
|
||||||
|
SETTINGS: 'settings',
|
||||||
|
PROFILE: 'profile',
|
||||||
|
UPGRADE: 'upgrade',
|
||||||
|
SUPPORT: 'support',
|
||||||
|
|
||||||
|
// Fallback
|
||||||
|
HOME: 'home',
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
// Singleton instance for app-wide use
|
||||||
|
export const deepLinkRouter = new DeepLinkRouter();
|
||||||
@ -169,3 +169,17 @@ export function createUseBroadcast(client: BroadcastClient) {
|
|||||||
return { client };
|
return { client };
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Deep Link Router
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
export {
|
||||||
|
DeepLinkRouter,
|
||||||
|
deepLinkRouter,
|
||||||
|
DeepLinkScreens,
|
||||||
|
createBroadcastDeepLink,
|
||||||
|
type DeepLinkRoute,
|
||||||
|
type DeepLinkHandler,
|
||||||
|
} from './deep-link.js';
|
||||||
|
|
||||||
|
|||||||
@ -20,6 +20,16 @@ import { BreadcrumbTrail } from './breadcrumbs.js';
|
|||||||
import { NetworkInterceptor } from './network.js';
|
import { NetworkInterceptor } from './network.js';
|
||||||
import { collectDeviceState } from './device.js';
|
import { collectDeviceState } from './device.js';
|
||||||
|
|
||||||
|
// DOM type declarations for ESLint
|
||||||
|
type ErrorEvent = {
|
||||||
|
message: string;
|
||||||
|
filename: string;
|
||||||
|
lineno: number;
|
||||||
|
colno: number;
|
||||||
|
error?: { stack?: string };
|
||||||
|
};
|
||||||
|
type EventListener = (event: unknown) => void;
|
||||||
|
|
||||||
export interface DiagnosticsClientOptions extends DiagnosticsConfig {
|
export interface DiagnosticsClientOptions extends DiagnosticsConfig {
|
||||||
/** Custom logger */
|
/** Custom logger */
|
||||||
logger?: {
|
logger?: {
|
||||||
|
|||||||
@ -6,6 +6,26 @@
|
|||||||
|
|
||||||
import type { DeviceState } from './types.js';
|
import type { DeviceState } from './types.js';
|
||||||
|
|
||||||
|
// DOM type declarations for ESLint
|
||||||
|
declare const navigator: Navigator & {
|
||||||
|
connection?: NetworkInformation;
|
||||||
|
getBattery?: () => Promise<BatteryManager>;
|
||||||
|
storage?: { estimate(): Promise<{ usage?: number }> };
|
||||||
|
};
|
||||||
|
declare const performance: Performance & { memory?: { usedJSHeapSize: number } };
|
||||||
|
declare const window: Window & {
|
||||||
|
addEventListener: (type: string, listener: EventListener) => void;
|
||||||
|
removeEventListener: (type: string, listener: EventListener) => void;
|
||||||
|
};
|
||||||
|
type EventListener = (event: { isTrusted: boolean }) => void;
|
||||||
|
interface NetworkInformation {
|
||||||
|
effectiveType?: string;
|
||||||
|
}
|
||||||
|
interface BatteryManager {
|
||||||
|
charging: boolean;
|
||||||
|
level: number;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collect current device state
|
* Collect current device state
|
||||||
* Best-effort: some APIs may not be available in all environments
|
* Best-effort: some APIs may not be available in all environments
|
||||||
|
|||||||
@ -6,6 +6,10 @@
|
|||||||
|
|
||||||
import type { NetworkRequest } from './types.js';
|
import type { NetworkRequest } from './types.js';
|
||||||
|
|
||||||
|
// DOM type declarations for ESLint
|
||||||
|
type RequestInfo = string | Request | URL;
|
||||||
|
type HeadersInit = Headers | Record<string, string> | string[][];
|
||||||
|
|
||||||
export interface NetworkInterceptorOptions {
|
export interface NetworkInterceptorOptions {
|
||||||
/** URL patterns to include (default: all) */
|
/** URL patterns to include (default: all) */
|
||||||
includePatterns?: RegExp[];
|
includePatterns?: RegExp[];
|
||||||
|
|||||||
@ -0,0 +1,172 @@
|
|||||||
|
package com.bytelyst.platform
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import android.util.Log
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deep Link Route data class
|
||||||
|
*/
|
||||||
|
data class DeepLinkRoute(
|
||||||
|
val screen: String,
|
||||||
|
val params: Map<String, String> = emptyMap()
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deep link handler type alias
|
||||||
|
*/
|
||||||
|
typealias DeepLinkHandler = (DeepLinkRoute) -> Unit
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deep Link Router class
|
||||||
|
* Handles routing from push notification deep links to app screens
|
||||||
|
*/
|
||||||
|
class DeepLinkRouter {
|
||||||
|
private val handlers = mutableMapOf<String, DeepLinkHandler>()
|
||||||
|
private var fallbackHandler: DeepLinkHandler? = null
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "DeepLinkRouter"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a handler for a specific screen
|
||||||
|
*/
|
||||||
|
fun register(screen: String, handler: DeepLinkHandler) {
|
||||||
|
handlers[screen] = handler
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a fallback handler for unregistered screens
|
||||||
|
*/
|
||||||
|
fun setFallback(handler: DeepLinkHandler) {
|
||||||
|
fallbackHandler = handler
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a deep link URL and extract route
|
||||||
|
*/
|
||||||
|
fun parseDeepLink(urlString: String): DeepLinkRoute? {
|
||||||
|
return try {
|
||||||
|
val uri = Uri.parse(urlString)
|
||||||
|
|
||||||
|
// Handle app-specific URLs: myapp://screen/params
|
||||||
|
if (uri.scheme != "http" && uri.scheme != "https") {
|
||||||
|
val pathSegments = uri.pathSegments
|
||||||
|
val screen = pathSegments.firstOrNull() ?: "home"
|
||||||
|
|
||||||
|
val params = mutableMapOf<String, String>()
|
||||||
|
uri.queryParameterNames.forEach { key ->
|
||||||
|
uri.getQueryParameter(key)?.let { value ->
|
||||||
|
params[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DeepLinkRoute(screen, params)
|
||||||
|
}
|
||||||
|
// Handle web URLs with deep link params
|
||||||
|
else if (uri.getQueryParameter("dl") != null) {
|
||||||
|
parseDeepLink(uri.getQueryParameter("dl")!!)
|
||||||
|
}
|
||||||
|
// Handle path-based routing: /screen/params
|
||||||
|
else {
|
||||||
|
val pathSegments = uri.pathSegments
|
||||||
|
if (pathSegments.isNotEmpty()) {
|
||||||
|
val screen = pathSegments[0]
|
||||||
|
|
||||||
|
val params = mutableMapOf<String, String>()
|
||||||
|
uri.queryParameterNames.forEach { key ->
|
||||||
|
uri.getQueryParameter(key)?.let { value ->
|
||||||
|
params[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DeepLinkRoute(screen, params)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.w(TAG, "Failed to parse deep link: $urlString", e)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a deep link route
|
||||||
|
*/
|
||||||
|
fun handle(route: DeepLinkRoute): Boolean {
|
||||||
|
val handler = handlers[route.screen]
|
||||||
|
|
||||||
|
return if (handler != null) {
|
||||||
|
handler(route)
|
||||||
|
true
|
||||||
|
} else if (fallbackHandler != null) {
|
||||||
|
fallbackHandler?.invoke(route)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "No handler for screen: ${route.screen}")
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process a deep link URL end-to-end
|
||||||
|
*/
|
||||||
|
fun process(urlString: String): Boolean {
|
||||||
|
val route = parseDeepLink(urlString)
|
||||||
|
return if (route != null) {
|
||||||
|
handle(route)
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Failed to parse deep link: $urlString")
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a broadcast deep link URL
|
||||||
|
*/
|
||||||
|
fun createBroadcastDeepLink(
|
||||||
|
baseUrl: String,
|
||||||
|
screen: String,
|
||||||
|
params: Map<String, String> = emptyMap(),
|
||||||
|
broadcastId: String? = null
|
||||||
|
): String {
|
||||||
|
val uriBuilder = Uri.parse(baseUrl).buildUpon()
|
||||||
|
.path("/$screen")
|
||||||
|
|
||||||
|
params.forEach { (key, value) ->
|
||||||
|
uriBuilder.appendQueryParameter(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
broadcastId?.let {
|
||||||
|
uriBuilder.appendQueryParameter("broadcastId", it)
|
||||||
|
}
|
||||||
|
|
||||||
|
return uriBuilder.build().toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common deep link screens
|
||||||
|
*/
|
||||||
|
object DeepLinkScreens {
|
||||||
|
// Broadcasts
|
||||||
|
const val BROADCAST = "broadcast"
|
||||||
|
const val ANNOUNCEMENTS = "announcements"
|
||||||
|
|
||||||
|
// Surveys
|
||||||
|
const val SURVEY = "survey"
|
||||||
|
const val SURVEY_LIST = "surveys"
|
||||||
|
|
||||||
|
// Product-specific
|
||||||
|
const val SETTINGS = "settings"
|
||||||
|
const val PROFILE = "profile"
|
||||||
|
const val UPGRADE = "upgrade"
|
||||||
|
const val SUPPORT = "support"
|
||||||
|
|
||||||
|
// Fallback
|
||||||
|
const val HOME = "home"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Singleton instance for app-wide use
|
||||||
|
val deepLinkRouter = DeepLinkRouter()
|
||||||
181
packages/swift-platform-sdk/Sources/BLDeepLinkRouter.swift
Normal file
181
packages/swift-platform-sdk/Sources/BLDeepLinkRouter.swift
Normal file
@ -0,0 +1,181 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deep Link Router — Swift
|
||||||
|
* Handles routing from push notification deep links to app screens
|
||||||
|
*/
|
||||||
|
|
||||||
|
public struct BLDeepLinkRoute {
|
||||||
|
public let screen: String
|
||||||
|
public let params: [String: String]
|
||||||
|
|
||||||
|
public init(screen: String, params: [String: String] = [:]) {
|
||||||
|
self.screen = screen
|
||||||
|
self.params = params
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public typealias BLDeepLinkHandler = (BLDeepLinkRoute) -> Void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deep Link Router class
|
||||||
|
*/
|
||||||
|
@available(iOS 15.0, *)
|
||||||
|
public class BLDeepLinkRouter {
|
||||||
|
private var handlers: [String: BLDeepLinkHandler] = [:]
|
||||||
|
private var fallbackHandler: BLDeepLinkHandler?
|
||||||
|
|
||||||
|
public init() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a handler for a specific screen
|
||||||
|
*/
|
||||||
|
public func register(screen: String, handler: @escaping BLDeepLinkHandler) {
|
||||||
|
handlers[screen] = handler
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a fallback handler for unregistered screens
|
||||||
|
*/
|
||||||
|
public func setFallback(handler: @escaping BLDeepLinkHandler) {
|
||||||
|
fallbackHandler = handler
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a deep link URL and extract route
|
||||||
|
*/
|
||||||
|
public func parseDeepLink(_ urlString: String) -> BLDeepLinkRoute? {
|
||||||
|
guard let url = URL(string: urlString) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle app-specific URLs: myapp://screen/params
|
||||||
|
if url.scheme != "http" && url.scheme != "https" {
|
||||||
|
let pathComponents = url.pathComponents.filter { $0 != "/" && !$0.isEmpty }
|
||||||
|
let screen = pathComponents.first ?? "home"
|
||||||
|
|
||||||
|
var params: [String: String] = [:]
|
||||||
|
if let queryItems = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems {
|
||||||
|
for item in queryItems {
|
||||||
|
if let value = item.value {
|
||||||
|
params[item.name] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return BLDeepLinkRoute(screen: screen, params: params)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle web URLs with deep link params
|
||||||
|
if let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
|
||||||
|
let dlParam = components.queryItems?.first(where: { $0.name == "dl" })?.value {
|
||||||
|
return parseDeepLink(dlParam)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle path-based routing: /screen/params
|
||||||
|
let pathComponents = url.pathComponents.filter { $0 != "/" && !$0.isEmpty }
|
||||||
|
if !pathComponents.isEmpty {
|
||||||
|
let screen = pathComponents[0]
|
||||||
|
|
||||||
|
var params: [String: String] = [:]
|
||||||
|
if let queryItems = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems {
|
||||||
|
for item in queryItems {
|
||||||
|
if let value = item.value {
|
||||||
|
params[item.name] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return BLDeepLinkRoute(screen: screen, params: params)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a deep link route
|
||||||
|
*/
|
||||||
|
@discardableResult
|
||||||
|
public func handle(_ route: BLDeepLinkRoute) -> Bool {
|
||||||
|
if let handler = handlers[route.screen] {
|
||||||
|
handler(route)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if let fallback = fallbackHandler {
|
||||||
|
fallback(route)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.deepLink.warning("No handler for screen: \(route.screen)")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process a deep link URL end-to-end
|
||||||
|
*/
|
||||||
|
@discardableResult
|
||||||
|
public func process(_ urlString: String) -> Bool {
|
||||||
|
guard let route = parseDeepLink(urlString) else {
|
||||||
|
Logger.deepLink.warning("Failed to parse deep link: \(urlString)")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return handle(route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a broadcast deep link URL
|
||||||
|
*/
|
||||||
|
public func createBroadcastDeepLink(
|
||||||
|
baseURL: String,
|
||||||
|
screen: String,
|
||||||
|
params: [String: String] = [:],
|
||||||
|
broadcastId: String? = nil
|
||||||
|
) -> String? {
|
||||||
|
guard var components = URLComponents(string: baseURL) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
components.path = "/\(screen)"
|
||||||
|
|
||||||
|
var queryItems: [URLQueryItem] = params.map { URLQueryItem(name: $0.key, value: $0.value) }
|
||||||
|
|
||||||
|
if let broadcastId = broadcastId {
|
||||||
|
queryItems.append(URLQueryItem(name: "broadcastId", value: broadcastId))
|
||||||
|
}
|
||||||
|
|
||||||
|
if !queryItems.isEmpty {
|
||||||
|
components.queryItems = queryItems
|
||||||
|
}
|
||||||
|
|
||||||
|
return components.string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Common deep link screens
|
||||||
|
*/
|
||||||
|
public struct BLDeepLinkScreens {
|
||||||
|
// Broadcasts
|
||||||
|
public static let broadcast = "broadcast"
|
||||||
|
public static let announcements = "announcements"
|
||||||
|
|
||||||
|
// Surveys
|
||||||
|
public static let survey = "survey"
|
||||||
|
public static let surveyList = "surveys"
|
||||||
|
|
||||||
|
// Product-specific
|
||||||
|
public static let settings = "settings"
|
||||||
|
public static let profile = "profile"
|
||||||
|
public static let upgrade = "upgrade"
|
||||||
|
public static let support = "support"
|
||||||
|
|
||||||
|
// Fallback
|
||||||
|
public static let home = "home"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logger extension
|
||||||
|
@available(iOS 15.0, *)
|
||||||
|
extension Logger {
|
||||||
|
static let deepLink = Logger(subsystem: "com.bytelyst.platform", category: "DeepLink")
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user