linkio 1.0.0

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 64d4ce7d3188df34bc7eca2fe8a757d69549a5a259c58f0f0a62cc5b908a5e6b
4
+ data.tar.gz: 15560070030e575ba7a08bf68f1e8ee90f31de139118c24b19282a192c502555
5
+ SHA512:
6
+ metadata.gz: a61cffbb4f5f2a7a7f406a90c8f91e8c920c04be3d572c1f2c1050ef22e340f4771d0cf80cc6d0451b6cb8e87ca369d4228935909bce11be2524976cf4da9bc0
7
+ data.tar.gz: 8944ecce09f0a936c3623e6aa88cd28ecddeffcfb96f052d167eed4c5cda2af59fddbb31333e7381eee90ca9ec724a7a678572762b7759c94a62b281d24e82e9
data/CHANGELOG.md ADDED
@@ -0,0 +1,47 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Added
11
+
12
+ - **Role-based multi-app routing.** Register multiple app targets with
13
+ `config.app "role" do |app| … end` and route on a configurable query/path
14
+ param (`config.role_param`, default `"role"`, with `config.default_role` as
15
+ the fallback). One link such as `/link?code=ABC&role=vendor` now opens the
16
+ matching app, falls back to that role's store on mobile, and redirects per
17
+ role on web.
18
+ - Per-target `fallback_url` with `{param}` templating for role-specific
19
+ redirects when the app is not installed (or for web visitors).
20
+ - `apple-app-site-association` and `assetlinks.json` now aggregate an entry for
21
+ every configured app target.
22
+ - `LinkIO::AppTarget` and `LinkIO::Utils.interpolate`.
23
+
24
+ ### Changed
25
+
26
+ - Single-app (flat) configuration is now the one-target special case of the
27
+ multi-app model and remains fully backward compatible.
28
+
29
+ ## [1.0.0] - 2026-07-08
30
+
31
+ ### Added
32
+
33
+ - Initial release — Ruby port of the Node.js LinkIO backend.
34
+ - Framework-agnostic `LinkIO::Client` with deep link handling, deferred deep
35
+ linking (IP fingerprint + device id), referral tracking, and generation of
36
+ `apple-app-site-association` and `assetlinks.json` documents.
37
+ - Smart redirect page that attempts to open the native app before falling back
38
+ to the App Store / Play Store.
39
+ - Pluggable storage: `LinkIO::Storage::InMemoryStorage`,
40
+ `LinkIO::Storage::RedisStorage`, and a `LinkIO::Storage::Base` interface for
41
+ custom adapters. Redis keys are namespaced identically to the Node.js backend.
42
+ - Mountable Rails engine (`LinkIO::Engine`) with controllers and routes, plus a
43
+ `linkio:install` generator.
44
+ - Rack middleware (`LinkIO::Rack::Middleware`) for non-Rails apps.
45
+ - Full RSpec test suite and RuboCop configuration.
46
+
47
+ [1.0.0]: https://github.com/pt-nakul-sharma/linkio-rails/releases/tag/v1.0.0
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nakul Sharma
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,295 @@
1
+ # LinkIO (Ruby)
2
+
3
+ **Self-hosted deep linking for mobile apps — an open-source alternative to Branch.io.**
4
+
5
+ LinkIO is the Ruby port of [LinkIO-Backend](https://github.com/pt-nakul-sharma/LinkIO-Backend) (Node.js). It gives you universal/app links, smart app-or-store redirects, fingerprint-based deferred deep linking, and referral tracking — with a framework-agnostic core, a mountable **Rails engine**, and **Rack middleware**.
6
+
7
+ [![CI](https://github.com/pt-nakul-sharma/linkio-rails/actions/workflows/ci.yml/badge.svg)](https://github.com/pt-nakul-sharma/linkio-rails/actions/workflows/ci.yml)
8
+ [![Gem Version](https://img.shields.io/gem/v/linkio.svg)](https://rubygems.org/gems/linkio)
9
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
10
+
11
+ ---
12
+
13
+ ## Features
14
+
15
+ - **Universal Links (iOS)** — automatic `apple-app-site-association` generation
16
+ - **App Links (Android)** — automatic `assetlinks.json` generation
17
+ - **Smart redirect** — tries to open the installed app, then falls back to the App Store / Play Store after a configurable timeout
18
+ - **Deferred deep linking** — IP-fingerprint matching preserves link data across the browser → install → app-open journey (works even when User-Agents differ)
19
+ - **Referral tracking** — record and query referrer → referee relationships
20
+ - **Role-based multi-app routing** — one link (`?code=…&role=…`) can open different apps (e.g. a User app vs a Vendor app) and fall back per role
21
+ - **Pluggable storage** — in-memory, Redis, or your own adapter
22
+ - **Three ways to mount** — plain Ruby, a Rails engine, or Rack middleware
23
+ - Fully tested with RSpec, zero required runtime dependencies
24
+ - **Runs on Ruby 3.0 through 3.4 and `head`**
25
+
26
+ ## Installation
27
+
28
+ ```ruby
29
+ # Gemfile
30
+ gem "linkio"
31
+ ```
32
+
33
+ ```bash
34
+ bundle install
35
+ # or
36
+ gem install linkio
37
+ ```
38
+
39
+ Requires Ruby >= 3.0 (tested on 3.0–3.4 and `head`).
40
+
41
+ ---
42
+
43
+ ## Quick start (Rails)
44
+
45
+ LinkIO ships a mountable engine. Generate the initializer:
46
+
47
+ ```bash
48
+ rails generate linkio:install
49
+ ```
50
+
51
+ Fill in your identifiers in `config/initializers/linkio.rb`:
52
+
53
+ ```ruby
54
+ LinkIO.configure do |config|
55
+ config.domain = "yourdomain.com"
56
+
57
+ # iOS
58
+ config.ios_app_id = "123456789"
59
+ config.ios_team_id = "TEAMID123"
60
+ config.ios_bundle_id = "com.yourapp"
61
+ config.ios_app_scheme = "yourapp" # yourapp://
62
+
63
+ # Android
64
+ config.android_package_name = "com.yourapp"
65
+ config.android_app_scheme = "yourapp" # yourapp://
66
+ config.android_sha256_fingerprints = ["AA:BB:CC:...:99"]
67
+
68
+ config.fallback_timeout = 2500 # ms before falling back to the store
69
+
70
+ # Development. Use RedisStorage in production (see below).
71
+ config.storage = LinkIO::Storage::InMemoryStorage.new
72
+ end
73
+ ```
74
+
75
+ Mount the engine **at the domain root** (so the `/.well-known` files resolve):
76
+
77
+ ```ruby
78
+ # config/routes.rb
79
+ mount LinkIO::Engine => "/"
80
+ ```
81
+
82
+ That exposes:
83
+
84
+ | Method | Path | Purpose |
85
+ | ------ | ---- | ------- |
86
+ | GET | `/.well-known/apple-app-site-association` | iOS universal-link verification |
87
+ | GET | `/.well-known/assetlinks.json` | Android app-link verification |
88
+ | GET | `/link` | Generic deep link handler (any query params) |
89
+ | GET | `/pending-link` | Retrieve deferred link by client IP fingerprint |
90
+ | GET | `/pending-link/:device_id` | Retrieve deferred link by device id |
91
+ | POST | `/track-referral` | Record a referral (`referralCode`, `userId`, `metadata`) |
92
+ | GET | `/referrals/:referrer_id` | List referrals for a referrer |
93
+
94
+ ---
95
+
96
+ ## Quick start (Rack / Sinatra / any Rack app)
97
+
98
+ ```ruby
99
+ # config.ru
100
+ require "linkio"
101
+ require "redis"
102
+
103
+ LinkIO.configure do |config|
104
+ config.domain = "yourdomain.com"
105
+ config.ios_app_id = "123456789"
106
+ config.ios_team_id = "TEAMID123"
107
+ config.ios_bundle_id = "com.yourapp"
108
+ config.ios_app_scheme = "yourapp"
109
+ config.android_package_name = "com.yourapp"
110
+ config.android_app_scheme = "yourapp"
111
+ config.android_sha256_fingerprints = ["AA:BB:CC:...:99"]
112
+ config.storage = LinkIO::Storage::RedisStorage.new(Redis.new)
113
+ end
114
+
115
+ use LinkIO::Rack::Middleware # uses LinkIO.client by default
116
+ run ->(_env) { [404, { "content-type" => "text/plain" }, ["Not found"]] }
117
+ ```
118
+
119
+ The middleware handles the LinkIO routes and passes everything else through to your app. Pass an explicit client with `use LinkIO::Rack::Middleware, client: my_client`.
120
+
121
+ ---
122
+
123
+ ## Using the core directly
124
+
125
+ The engine and middleware are thin wrappers around `LinkIO::Client`, which you can use anywhere:
126
+
127
+ ```ruby
128
+ client = LinkIO::Client.new(
129
+ domain: "yourdomain.com",
130
+ ios_app_id: "123456789",
131
+ ios_team_id: "TEAMID123",
132
+ ios_bundle_id: "com.yourapp",
133
+ ios_app_scheme: "yourapp",
134
+ android_package_name: "com.yourapp",
135
+ android_app_scheme: "yourapp",
136
+ android_sha256_fingerprints: ["AA:BB:CC:...:99"],
137
+ storage: LinkIO::Storage::InMemoryStorage.new
138
+ )
139
+
140
+ # Handle a deep link — returns a LinkIO::Result (redirect / html / json)
141
+ request = LinkIO::Request.from_rack(env) # or build one yourself
142
+ result = client.handle_deep_link(request)
143
+ result.redirect? # => true/false
144
+ result.location # => store URL (for redirects)
145
+ result.body # => HTML string or Ruby hash (json)
146
+
147
+ # Deferred deep linking (called by your mobile SDK after install)
148
+ client.pending_link_by_fingerprint(client_ip) # => LinkIO::DeepLinkData | nil
149
+ client.pending_link(device_id) # => LinkIO::DeepLinkData | nil
150
+
151
+ # Referrals
152
+ client.track_referral("CODE123", "user-42", { campaign: "summer" })
153
+ client.referrals("CODE123") # => [LinkIO::ReferralData]
154
+ client.referral_for_user("user-42") # => LinkIO::ReferralData | nil
155
+
156
+ # Verification documents
157
+ client.apple_app_site_association # => Hash
158
+ client.asset_links # => Array<Hash>
159
+ ```
160
+
161
+ `LinkIO::Request` fields: `user_agent`, `full_url`, `query_params`, `path_params`, `client_ip`, `device_id`. Build one from a Rack env with `LinkIO::Request.from_rack(env)`, or construct it directly for full control.
162
+
163
+ ---
164
+
165
+ ## Role-based routing (multiple apps)
166
+
167
+ Have more than one app — say a **User** app and a **Vendor** app — and want a single referral link to open the right one? Register an app target per role and LinkIO routes on a query/path param (default `role`):
168
+
169
+ ```ruby
170
+ LinkIO.configure do |config|
171
+ config.domain = "rokart.in"
172
+ config.role_param = "role" # which param selects the app (default: "role")
173
+ config.default_role = "user" # used when role is missing or unknown
174
+
175
+ config.app "user" do |app|
176
+ app.ios_app_id = "111111111"
177
+ app.ios_team_id = "TEAMID123"
178
+ app.ios_bundle_id = "com.rokart.user"
179
+ app.ios_app_scheme = "rokartuser"
180
+ app.android_package_name = "com.rokart.user"
181
+ app.android_app_scheme = "rokartuser"
182
+ app.android_sha256_fingerprints = ["AA:BB:...:99"]
183
+ # Optional: where to send visitors when the app isn't installed (per role).
184
+ app.fallback_url = "https://rokart.in/refer?code={code}&role=user"
185
+ end
186
+
187
+ config.app "vendor" do |app|
188
+ app.ios_app_id = "222222222"
189
+ app.ios_team_id = "TEAMID123"
190
+ app.ios_bundle_id = "com.rokart.vendor"
191
+ app.ios_app_scheme = "rokartvendor"
192
+ app.android_package_name = "com.rokart.vendor"
193
+ app.android_app_scheme = "rokartvendor"
194
+ app.android_sha256_fingerprints = ["CC:DD:...:11"]
195
+ app.fallback_url = "https://rokart.in/refer?code={code}&role=vendor"
196
+ end
197
+
198
+ config.storage = LinkIO::Storage::InMemoryStorage.new
199
+ end
200
+ ```
201
+
202
+ Now share `https://rokart.in/link?code=ABC123&role=vendor`:
203
+
204
+ | Visitor | Behaviour |
205
+ | ------- | --------- |
206
+ | iOS/Android with the **Vendor** app | Smart-redirect page opens `rokartvendor://link?code=ABC123&role=vendor` |
207
+ | Mobile **without** the app installed | Falls back to that role's `fallback_url` (or, if none set, that role's App Store / Play Store listing) |
208
+ | Desktop / web | Redirects to the role's `fallback_url` (or returns a JSON "open on mobile" message if none set) |
209
+ | `role` missing or unrecognised | Uses `default_role` (here, `user`) |
210
+
211
+ **Fallback URL templating.** `fallback_url` supports `{param}` placeholders filled (and URL-encoded) from the link's query — e.g. `https://rokart.in/refer?code={code}&role=vendor` becomes `…?code=ABC123&role=vendor`. Any query param can be interpolated, so this is fully generic — add `region`, `campaign`, etc. as needed.
212
+
213
+ **Verification files.** `apple-app-site-association` and `assetlinks.json` automatically include an entry for **every** registered app target, so both apps verify from the one domain.
214
+
215
+ The single-app (flat) configuration shown earlier is just the special case of one target — everything above still applies with `role` simply ignored.
216
+
217
+ ---
218
+
219
+ ## Storage
220
+
221
+ Any object implementing the `LinkIO::Storage::Base` interface can back LinkIO.
222
+
223
+ ### In-memory (development)
224
+
225
+ ```ruby
226
+ config.storage = LinkIO::Storage::InMemoryStorage.new
227
+ ```
228
+
229
+ Process-local and thread-safe. Data is lost on restart and not shared between workers — don't use it in production.
230
+
231
+ ### Redis (production)
232
+
233
+ ```ruby
234
+ require "redis"
235
+ config.storage = LinkIO::Storage::RedisStorage.new(Redis.new)
236
+ ```
237
+
238
+ Keys are namespaced (`linkio:pending:*`, `linkio:fingerprint:*`, `linkio:referral:*`, `linkio:referrer:*`) identically to the Node.js backend, so both implementations can share a Redis. Pending links are written with a TTL and expire automatically.
239
+
240
+ ### Custom
241
+
242
+ Subclass `LinkIO::Storage::Base` and implement the nine methods (`save_pending_link`, `get_pending_link`, `delete_pending_link`, the three `*_by_fingerprint` variants, `save_referral`, `get_referrals_by_referrer`, `get_referral_by_referee`). Pending-link methods take/return `LinkIO::PendingLinkData`; referral methods take/return `LinkIO::ReferralData`. Both expose `#to_h` / `.from_h` for JSON-compatible (camelCase) serialization.
243
+
244
+ ---
245
+
246
+ ## Configuration reference
247
+
248
+ **Top-level options:**
249
+
250
+ | Option | Required | Default | Description |
251
+ | ------ | -------- | ------- | ----------- |
252
+ | `domain` | ✅ | — | Your linking domain |
253
+ | `storage` | ✅ | — | A `LinkIO::Storage::Base` implementation |
254
+ | `role_param` | | `"role"` | Query/path param used to select an app target |
255
+ | `default_role` | | — | App target used when `role_param` is missing/unknown |
256
+ | `fallback_timeout` | | `2500` | ms before the redirect page falls back |
257
+ | `default_deep_link_path` | | — | Reserved for future use |
258
+
259
+ **Per-app-target options** (set directly on `config` for a single app, or inside `config.app "role" do |app| … end` for multiple):
260
+
261
+ | Option | Required | Default | Description |
262
+ | ------ | -------- | ------- | ----------- |
263
+ | `ios_app_id` | ✅ | — | Apple numeric app id (App Store URL) |
264
+ | `ios_team_id` | ✅ | — | Apple developer Team ID (AASA `appID`) |
265
+ | `ios_bundle_id` | ✅ | — | iOS bundle identifier (AASA `appID`) |
266
+ | `ios_app_scheme` | | — | Custom scheme, e.g. `yourapp` → `yourapp://` |
267
+ | `android_package_name` | ✅ | — | Android package name |
268
+ | `android_app_scheme` | | — | Custom scheme for Android |
269
+ | `android_sha256_fingerprints` | | `[]` | SHA-256 signing cert fingerprints for `assetlinks.json` |
270
+ | `fallback_url` | | — | Per-role destination when the app isn't installed / on web; supports `{param}` templating |
271
+
272
+ If `ios_app_scheme` / `android_app_scheme` is omitted, LinkIO redirects straight to the fallback (store or `fallback_url`) instead of rendering the smart-redirect page.
273
+
274
+ ---
275
+
276
+ ## Ecosystem
277
+
278
+ LinkIO pairs with the platform SDKs: **LinkIO-iOS**, **LinkIO-Android**, and **LinkIO-React-Native**, plus the Node.js backend it is ported from.
279
+
280
+ ## Development
281
+
282
+ ```bash
283
+ bin/setup # bundle install
284
+ bundle exec rspec # run tests
285
+ bundle exec rubocop # lint
286
+ bundle exec rake # both
287
+ ```
288
+
289
+ ## Contributing
290
+
291
+ Bug reports and pull requests are welcome at <https://github.com/pt-nakul-sharma/linkio-rails>.
292
+
293
+ ## License
294
+
295
+ Released under the [MIT License](LICENSE).
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkIO
4
+ # Base controller for the engine. Provides access to the configured client
5
+ # and a helper to render a {LinkIO::Result}.
6
+ class ApplicationController < ActionController::Base
7
+ protect_from_forgery with: :null_session
8
+
9
+ private
10
+
11
+ # @return [LinkIO::Client]
12
+ def linkio_client
13
+ LinkIO.client
14
+ end
15
+
16
+ # Build a framework-agnostic {LinkIO::Request} from the Rails request.
17
+ #
18
+ # @return [LinkIO::Request]
19
+ def linkio_request
20
+ ip = LinkIO::Utils.client_ip(
21
+ forwarded_for: request.headers["X-Forwarded-For"],
22
+ ip: request.remote_ip,
23
+ remote_address: request.remote_addr
24
+ )
25
+ device_id = params[:deviceId] || request.headers["X-Device-Id"]
26
+
27
+ LinkIO::Request.new(
28
+ user_agent: request.user_agent,
29
+ full_url: request.original_url,
30
+ query_params: request.query_parameters,
31
+ path_params: request.path_parameters.except(:controller, :action),
32
+ client_ip: ip,
33
+ device_id: device_id
34
+ )
35
+ end
36
+
37
+ # Render a {LinkIO::Result} through the Rails response.
38
+ #
39
+ # @param result [LinkIO::Result]
40
+ def render_linkio(result)
41
+ if result.redirect?
42
+ redirect_to result.location, status: result.status, allow_other_host: true
43
+ elsif result.json?
44
+ render json: result.body, status: result.status
45
+ else
46
+ render html: result.body.html_safe, status: result.status, content_type: result.headers["content-type"]
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkIO
4
+ # Handles incoming deep links (GET /link). Stores a pending link for deferred
5
+ # deep linking and redirects to the app or store based on platform.
6
+ class DeepLinksController < ApplicationController
7
+ def show
8
+ render_linkio(linkio_client.handle_deep_link(linkio_request))
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkIO
4
+ # Retrieves pending links for deferred deep linking.
5
+ class PendingLinksController < ApplicationController
6
+ # GET /pending-link/:device_id
7
+ def by_device
8
+ deep_link = linkio_client.pending_link(params[:device_id])
9
+ respond_with_deep_link(deep_link)
10
+ end
11
+
12
+ # GET /pending-link (matched by client IP fingerprint)
13
+ def by_fingerprint
14
+ ip = LinkIO::Utils.client_ip(
15
+ forwarded_for: request.headers["X-Forwarded-For"],
16
+ ip: request.remote_ip,
17
+ remote_address: request.remote_addr
18
+ )
19
+ deep_link = linkio_client.pending_link_by_fingerprint(ip, request.user_agent)
20
+ respond_with_deep_link(deep_link)
21
+ end
22
+
23
+ private
24
+
25
+ def respond_with_deep_link(deep_link)
26
+ if deep_link.nil?
27
+ render json: { error: "No pending link found" }, status: :not_found
28
+ else
29
+ render json: deep_link.to_h
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkIO
4
+ # Tracks and lists referrals.
5
+ class ReferralsController < ApplicationController
6
+ # POST /track-referral
7
+ # Body: { referralCode:, userId:, metadata: }
8
+ def create
9
+ linkio_client.track_referral(
10
+ params[:referralCode],
11
+ params[:userId],
12
+ params[:metadata]&.to_unsafe_h
13
+ )
14
+ render json: { success: true }
15
+ end
16
+
17
+ # GET /referrals/:referrer_id
18
+ def index
19
+ referrals = linkio_client.referrals(params[:referrer_id])
20
+ render json: { referrals: referrals.map(&:to_h) }
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LinkIO
4
+ # Serves the domain verification files required by iOS and Android.
5
+ class WellKnownController < ApplicationController
6
+ # GET /.well-known/apple-app-site-association
7
+ def apple_app_site_association
8
+ render json: linkio_client.apple_app_site_association
9
+ end
10
+
11
+ # GET /.well-known/assetlinks.json
12
+ def asset_links
13
+ render json: linkio_client.asset_links
14
+ end
15
+ end
16
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ LinkIO::Engine.routes.draw do
4
+ # Domain verification files (must be reachable at the domain root).
5
+ get "/.well-known/apple-app-site-association", to: "well_known#apple_app_site_association"
6
+ get "/.well-known/assetlinks.json", to: "well_known#asset_links"
7
+
8
+ # Generic deep link handler, e.g. /link?referralCode=ABC123
9
+ get "/link", to: "deep_links#show"
10
+
11
+ # Deferred deep linking.
12
+ get "/pending-link", to: "pending_links#by_fingerprint"
13
+ get "/pending-link/:device_id", to: "pending_links#by_device", constraints: { device_id: %r{[^/]+} }
14
+
15
+ # Referrals.
16
+ post "/track-referral", to: "referrals#create"
17
+ get "/referrals/:referrer_id", to: "referrals#index", constraints: { referrer_id: %r{[^/]+} }
18
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module LinkIO
6
+ module Generators
7
+ # Installs LinkIO into a Rails app: creates an initializer and prints the
8
+ # route to mount.
9
+ #
10
+ # rails generate linkio:install
11
+ class InstallGenerator < ::Rails::Generators::Base
12
+ source_root File.expand_path("templates", __dir__)
13
+
14
+ def create_initializer
15
+ template "linkio.rb", "config/initializers/linkio.rb"
16
+ end
17
+
18
+ def show_readme
19
+ say ""
20
+ say "LinkIO installed. Next steps:", :green
21
+ say " 1. Fill in your app identifiers in config/initializers/linkio.rb"
22
+ say " 2. Mount the engine at the domain root in config/routes.rb:"
23
+ say ""
24
+ say " mount LinkIO::Engine => \"/\""
25
+ say ""
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ # LinkIO configuration.
4
+ # Docs: https://github.com/pt-nakul-sharma/linkio-rails
5
+ LinkIO.configure do |config|
6
+ config.domain = "example.com"
7
+
8
+ # Wait this many ms in the redirect page before falling back to the store.
9
+ config.fallback_timeout = 2500
10
+
11
+ # --- Single app -----------------------------------------------------------
12
+ # iOS
13
+ config.ios_app_id = "123456789" # Apple app id (numeric)
14
+ config.ios_team_id = "TEAMID123" # Apple developer Team ID
15
+ config.ios_bundle_id = "com.example.app"
16
+ config.ios_app_scheme = "example" # custom URL scheme -> example://
17
+
18
+ # Android
19
+ config.android_package_name = "com.example.app"
20
+ config.android_app_scheme = "example" # custom URL scheme -> example://
21
+ config.android_sha256_fingerprints = [
22
+ "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99"
23
+ ]
24
+
25
+ # Optional per-role fallback used on web, and on mobile when the app is not
26
+ # installed. Supports {param} placeholders filled from the link's query.
27
+ # config.fallback_url = "https://example.com/download?code={code}"
28
+
29
+ # --- Multiple apps by role (e.g. a referral link ?code=ABC&role=vendor) ---
30
+ # Remove the single-app block above and register one target per role instead.
31
+ # The `role` query/path param selects which app to open.
32
+ #
33
+ # config.role_param = "role" # param used to route (default: "role")
34
+ # config.default_role = "user" # used when role is missing/unknown
35
+ #
36
+ # config.app "user" do |app|
37
+ # app.ios_app_id = "111111111"
38
+ # app.ios_team_id = "TEAMID123"
39
+ # app.ios_bundle_id = "com.example.user"
40
+ # app.ios_app_scheme = "exampleuser"
41
+ # app.android_package_name = "com.example.user"
42
+ # app.android_app_scheme = "exampleuser"
43
+ # app.android_sha256_fingerprints = ["AA:BB:...:99"]
44
+ # app.fallback_url = "https://example.com/refer?code={code}&role=user"
45
+ # end
46
+ #
47
+ # config.app "vendor" do |app|
48
+ # app.ios_app_id = "222222222"
49
+ # app.ios_team_id = "TEAMID123"
50
+ # app.ios_bundle_id = "com.example.vendor"
51
+ # app.ios_app_scheme = "examplevendor"
52
+ # app.android_package_name = "com.example.vendor"
53
+ # app.android_app_scheme = "examplevendor"
54
+ # app.android_sha256_fingerprints = ["CC:DD:...:11"]
55
+ # app.fallback_url = "https://example.com/refer?code={code}&role=vendor"
56
+ # end
57
+
58
+ # Storage. InMemoryStorage is for development only.
59
+ # For production use RedisStorage:
60
+ # require "redis"
61
+ # config.storage = LinkIO::Storage::RedisStorage.new(Redis.new)
62
+ config.storage = LinkIO::Storage::InMemoryStorage.new
63
+ end