@maccesar/titools 2.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.
Files changed (120) hide show
  1. package/AGENTS-TEMPLATE.md +173 -0
  2. package/README.md +867 -0
  3. package/agents/ti-researcher.md +108 -0
  4. package/bin/titools.js +53 -0
  5. package/lib/commands/agents.js +126 -0
  6. package/lib/commands/install.js +188 -0
  7. package/lib/commands/uninstall.js +215 -0
  8. package/lib/commands/update.js +159 -0
  9. package/lib/config.js +119 -0
  10. package/lib/downloader.js +153 -0
  11. package/lib/installer.js +253 -0
  12. package/lib/platform.js +108 -0
  13. package/lib/symlink.js +142 -0
  14. package/lib/utils.js +270 -0
  15. package/package.json +67 -0
  16. package/skills/alloy-expert/SKILL.md +247 -0
  17. package/skills/alloy-expert/assets/ControllerAutoCleanup.js +182 -0
  18. package/skills/alloy-expert/references/alloy-structure.md +381 -0
  19. package/skills/alloy-expert/references/anti-patterns.md +133 -0
  20. package/skills/alloy-expert/references/code-conventions.md +469 -0
  21. package/skills/alloy-expert/references/contracts.md +280 -0
  22. package/skills/alloy-expert/references/controller-patterns.md +520 -0
  23. package/skills/alloy-expert/references/error-handling.md +484 -0
  24. package/skills/alloy-expert/references/examples.md +735 -0
  25. package/skills/alloy-expert/references/migration-patterns.md +298 -0
  26. package/skills/alloy-expert/references/patterns.md +448 -0
  27. package/skills/alloy-expert/references/performance-patterns.md +855 -0
  28. package/skills/alloy-expert/references/security-patterns.md +847 -0
  29. package/skills/alloy-expert/references/state-management.md +779 -0
  30. package/skills/alloy-expert/references/testing.md +872 -0
  31. package/skills/alloy-guides/SKILL.md +214 -0
  32. package/skills/alloy-guides/references/CLI_TASKS.md +243 -0
  33. package/skills/alloy-guides/references/CONCEPTS.md +191 -0
  34. package/skills/alloy-guides/references/CONTROLLERS.md +298 -0
  35. package/skills/alloy-guides/references/MODELS.md +1028 -0
  36. package/skills/alloy-guides/references/PURGETSS.md +56 -0
  37. package/skills/alloy-guides/references/VIEWS_DYNAMIC.md +242 -0
  38. package/skills/alloy-guides/references/VIEWS_STYLES.md +388 -0
  39. package/skills/alloy-guides/references/VIEWS_WITHOUT_CONTROLLERS.md +109 -0
  40. package/skills/alloy-guides/references/VIEWS_XML.md +558 -0
  41. package/skills/alloy-guides/references/WIDGETS.md +176 -0
  42. package/skills/alloy-howtos/SKILL.md +203 -0
  43. package/skills/alloy-howtos/references/best_practices.md +138 -0
  44. package/skills/alloy-howtos/references/cli_reference.md +253 -0
  45. package/skills/alloy-howtos/references/config_files.md +87 -0
  46. package/skills/alloy-howtos/references/custom_tags.md +147 -0
  47. package/skills/alloy-howtos/references/debugging_troubleshooting.md +101 -0
  48. package/skills/alloy-howtos/references/samples.md +167 -0
  49. package/skills/purgetss/SKILL.md +442 -0
  50. package/skills/purgetss/assets/purgetss.config.cjs +17 -0
  51. package/skills/purgetss/references/EXAMPLES.md +247 -0
  52. package/skills/purgetss/references/animation-system.md +1294 -0
  53. package/skills/purgetss/references/apply-directive.md +375 -0
  54. package/skills/purgetss/references/arbitrary-values.md +612 -0
  55. package/skills/purgetss/references/class-index.md +1350 -0
  56. package/skills/purgetss/references/cli-commands.md +948 -0
  57. package/skills/purgetss/references/configurable-properties.md +654 -0
  58. package/skills/purgetss/references/custom-rules.md +161 -0
  59. package/skills/purgetss/references/customization-deep-dive.md +722 -0
  60. package/skills/purgetss/references/dynamic-component-creation.md +489 -0
  61. package/skills/purgetss/references/grid-layout.md +455 -0
  62. package/skills/purgetss/references/icon-fonts.md +609 -0
  63. package/skills/purgetss/references/installation-setup.md +366 -0
  64. package/skills/purgetss/references/opacity-modifier.md +291 -0
  65. package/skills/purgetss/references/platform-modifiers.md +479 -0
  66. package/skills/purgetss/references/smart-mappings.md +42 -0
  67. package/skills/purgetss/references/titanium-resets.md +359 -0
  68. package/skills/purgetss/references/ui-ux-design.md +1526 -0
  69. package/skills/ti-guides/SKILL.md +94 -0
  70. package/skills/ti-guides/references/advanced-data-and-images.md +19 -0
  71. package/skills/ti-guides/references/alloy-cli-advanced.md +84 -0
  72. package/skills/ti-guides/references/alloy-data-mastery.md +29 -0
  73. package/skills/ti-guides/references/alloy-widgets-and-themes.md +19 -0
  74. package/skills/ti-guides/references/android-manifest.md +97 -0
  75. package/skills/ti-guides/references/app-distribution.md +258 -0
  76. package/skills/ti-guides/references/application-frameworks.md +377 -0
  77. package/skills/ti-guides/references/cli-reference.md +402 -0
  78. package/skills/ti-guides/references/coding-best-practices.md +102 -0
  79. package/skills/ti-guides/references/commonjs-advanced.md +134 -0
  80. package/skills/ti-guides/references/hello-world.md +100 -0
  81. package/skills/ti-guides/references/hyperloop-native-access.md +62 -0
  82. package/skills/ti-guides/references/javascript-primer.md +411 -0
  83. package/skills/ti-guides/references/reserved-words.md +36 -0
  84. package/skills/ti-guides/references/resources.md +183 -0
  85. package/skills/ti-guides/references/style-and-conventions.md +48 -0
  86. package/skills/ti-guides/references/tiapp-config.md +609 -0
  87. package/skills/ti-howtos/SKILL.md +174 -0
  88. package/skills/ti-howtos/references/android-platform-deep-dives.md +658 -0
  89. package/skills/ti-howtos/references/automation-fastlane-appium.md +95 -0
  90. package/skills/ti-howtos/references/buffer-codec-streams.md +140 -0
  91. package/skills/ti-howtos/references/cross-platform-development.md +348 -0
  92. package/skills/ti-howtos/references/debugging-profiling.md +543 -0
  93. package/skills/ti-howtos/references/extending-titanium.md +723 -0
  94. package/skills/ti-howtos/references/google-maps-v2.md +169 -0
  95. package/skills/ti-howtos/references/ios-map-kit.md +143 -0
  96. package/skills/ti-howtos/references/ios-platform-deep-dives.md +783 -0
  97. package/skills/ti-howtos/references/local-data-sources.md +301 -0
  98. package/skills/ti-howtos/references/location-and-maps.md +252 -0
  99. package/skills/ti-howtos/references/media-apis.md +210 -0
  100. package/skills/ti-howtos/references/notification-services.md +599 -0
  101. package/skills/ti-howtos/references/remote-data-sources.md +349 -0
  102. package/skills/ti-howtos/references/tutorials.md +502 -0
  103. package/skills/ti-howtos/references/using-modules.md +237 -0
  104. package/skills/ti-howtos/references/web-content-integration.md +307 -0
  105. package/skills/ti-howtos/references/webpack-build-pipeline.md +78 -0
  106. package/skills/ti-ui/SKILL.md +179 -0
  107. package/skills/ti-ui/references/accessibility-deep-dive.md +242 -0
  108. package/skills/ti-ui/references/animation-and-matrices.md +599 -0
  109. package/skills/ti-ui/references/application-structures.md +655 -0
  110. package/skills/ti-ui/references/custom-fonts-styling.md +579 -0
  111. package/skills/ti-ui/references/event-handling.md +393 -0
  112. package/skills/ti-ui/references/gestures.md +473 -0
  113. package/skills/ti-ui/references/icons-and-splash-screens.md +409 -0
  114. package/skills/ti-ui/references/layouts-and-positioning.md +462 -0
  115. package/skills/ti-ui/references/listviews-and-performance.md +619 -0
  116. package/skills/ti-ui/references/orientation.md +362 -0
  117. package/skills/ti-ui/references/platform-ui-android.md +635 -0
  118. package/skills/ti-ui/references/platform-ui-ios.md +469 -0
  119. package/skills/ti-ui/references/scrolling-views.md +252 -0
  120. package/skills/ti-ui/references/tableviews.md +568 -0
@@ -0,0 +1,847 @@
1
+ # Security Patterns for Titanium Mobile Apps
2
+
3
+ ## Token Storage Strategy
4
+
5
+ **NEVER store tokens in:** `Ti.App.Properties` (plaintext), localStorage, or files.
6
+
7
+ **USE platform-specific secure storage:**
8
+
9
+ ```javascript
10
+ // lib/services/tokenStorage.js
11
+ exports.TokenStorage = {
12
+ save(token) {
13
+ if (Ti.Platform.osname === 'android') {
14
+ // Use Android KeyStore
15
+ const keyStore = Ti.Android.createKeyStore({
16
+ name: 'SecureKeyStore'
17
+ })
18
+ keyStore.addEntry('authToken', token)
19
+ } else {
20
+ // Use iOS Keychain
21
+ Ti.KeychainItem.setItem({
22
+ identifier: 'authToken',
23
+ value: token,
24
+ accessGroup: 'com.yourapp.keychain'
25
+ })
26
+ }
27
+ },
28
+
29
+ get() {
30
+ if (Ti.Platform.osname === 'android') {
31
+ const keyStore = Ti.Android.createKeyStore({
32
+ name: 'SecureKeyStore'
33
+ })
34
+ return keyStore.getEntry('authToken')
35
+ } else {
36
+ return Ti.KeychainItem.getItem({
37
+ identifier: 'authToken',
38
+ accessGroup: 'com.yourapp.keychain'
39
+ })
40
+ }
41
+ },
42
+
43
+ clear() {
44
+ if (Ti.Platform.osname === 'android') {
45
+ const keyStore = Ti.Android.createKeyStore({
46
+ name: 'SecureKeyStore'
47
+ })
48
+ keyStore.removeEntry('authToken')
49
+ } else {
50
+ Ti.KeychainItem.removeItem({
51
+ identifier: 'authToken',
52
+ accessGroup: 'com.yourapp.keychain'
53
+ })
54
+ }
55
+ }
56
+ }
57
+ ```
58
+
59
+ ## Certificate Pinning
60
+
61
+ Prevent man-in-the-middle attacks by pinning SSL certificates:
62
+
63
+ ```javascript
64
+ // lib/api/pinnedClient.js
65
+ exports.createPinnedClient = function() {
66
+ const client = Ti.Network.createHTTPClient({
67
+ // Security: Enable certificate pinning
68
+ certificatePinning: true,
69
+
70
+ // Specify allowed certificates
71
+ validatesSecureCertificate: true,
72
+
73
+ onload: () => {
74
+ // Success
75
+ },
76
+
77
+ onerror: (e) => {
78
+ // Certificate validation failed
79
+ if (e.error.indexOf('certificate') >= 0) {
80
+ Ti.API.error('Certificate pinning failed - possible MITM attack')
81
+ }
82
+ }
83
+ })
84
+
85
+ return client
86
+ }
87
+ ```
88
+
89
+ **Add certificates to tiapp.xml:**
90
+
91
+ ```xml
92
+ <ti:app>
93
+ <certificates>
94
+ <certificate>
95
+ <name>api.example.com</name>
96
+ <type>rsa</type>
97
+ <file>certificates/api-pin.pem</file>
98
+ </certificate>
99
+ </certificates>
100
+ </ti:app>
101
+ ```
102
+
103
+ ## Data Encryption at Rest
104
+
105
+ ```javascript
106
+ // lib/services/encryption.js
107
+ // AES-256 encryption for sensitive local data
108
+
109
+ const crypto = require('ti.crypto')
110
+
111
+ exports.encrypt = function(data, key) {
112
+ return crypto.encrypt({
113
+ data: data,
114
+ key: key,
115
+ algorithm: crypto.AES_256_CBC,
116
+ options: { mode: crypto.CBC }
117
+ })
118
+ }
119
+
120
+ exports.decrypt = function(encryptedData, key) {
121
+ return crypto.decrypt({
122
+ data: encryptedData,
123
+ key: key,
124
+ algorithm: crypto.AES_256_CBC,
125
+ options: { mode: crypto.CBC }
126
+ })
127
+ }
128
+
129
+ // Usage: Secure cache of sensitive user data
130
+ module.exports = class SecureCache {
131
+ constructor(encryptionKey) {
132
+ this.key = encryptionKey
133
+ this.cache = {}
134
+ }
135
+
136
+ set(key, value) {
137
+ const encrypted = encrypt(JSON.stringify(value), this.key)
138
+ this.cache[key] = encrypted
139
+ }
140
+
141
+ get(key) {
142
+ if (!this.cache[key]) return null
143
+
144
+ const decrypted = decrypt(this.cache[key], this.key)
145
+ return JSON.parse(decrypted)
146
+ }
147
+
148
+ clear() {
149
+ this.cache = {}
150
+ }
151
+ }
152
+ ```
153
+
154
+ ## Secure HTTP Communication
155
+
156
+ ```javascript
157
+ // lib/api/secureClient.js
158
+ exports.createSecureClient = function(baseUrl) {
159
+ return {
160
+ request(method, endpoint, data = null) {
161
+ return new Promise((resolve, reject) => {
162
+ const client = Ti.Network.createHTTPClient({
163
+ timeout: 10000,
164
+
165
+ onload: function() {
166
+ if (this.status === 200) {
167
+ try {
168
+ resolve(JSON.parse(this.responseText))
169
+ } catch (e) {
170
+ reject(new Error('Invalid JSON response'))
171
+ }
172
+ } else {
173
+ reject(new Error(`HTTP ${this.status}`))
174
+ }
175
+ },
176
+
177
+ onerror: function(e) {
178
+ // Log security events
179
+ if (this.status === 401 || this.status === 403) {
180
+ Ti.API.warn(`[SECURITY] Unauthorized: ${endpoint}`)
181
+ }
182
+ reject(e)
183
+ }
184
+ })
185
+
186
+ client.open(method, `${baseUrl}${endpoint}`)
187
+
188
+ // Security headers
189
+ client.setRequestHeader('User-Agent', `MyApp/${Ti.App.version}`)
190
+ client.setRequestHeader('Accept', 'application/json')
191
+ client.setRequestHeader('Content-Type', 'application/json')
192
+
193
+ client.send(data ? JSON.stringify(data) : null)
194
+ })
195
+ },
196
+
197
+ get(endpoint) {
198
+ return this.request('GET', endpoint)
199
+ },
200
+
201
+ post(endpoint, data) {
202
+ return this.request('POST', endpoint, data)
203
+ }
204
+ }
205
+ }
206
+ ```
207
+
208
+ ## Authentication Token Refresh Pattern
209
+
210
+ ```javascript
211
+ // lib/services/authService.js
212
+ const { TokenStorage } = require('lib/services/tokenStorage')
213
+
214
+ const TOKEN_REFRESH_THRESHOLD = 5 * 60 * 1000 // 5 minutes before expiry
215
+
216
+ exports.refreshAuthToken = async function() {
217
+ const refreshToken = TokenStorage.get('refreshToken')
218
+
219
+ const response = await api.post('/auth/refresh', {
220
+ refresh_token: refreshToken
221
+ })
222
+
223
+ TokenStorage.save(response.access_token)
224
+
225
+ // Set up auto-refresh
226
+ scheduleTokenRefresh(response.expires_in)
227
+ }
228
+
229
+ function scheduleTokenRefresh(expiresIn) {
230
+ const refreshTime = expiresIn - TOKEN_REFRESH_THRESHOLD
231
+
232
+ setTimeout(() => {
233
+ refreshAuthToken().catch(() => {
234
+ // Refresh failed - redirect to login
235
+ Alloy.createController('login').getView().open()
236
+ })
237
+ }, refreshTime)
238
+ }
239
+ ```
240
+
241
+ ## Input Validation
242
+
243
+ ```javascript
244
+ // lib/services/validator.js
245
+ exports.Validator = {
246
+ email(email) {
247
+ const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
248
+ if (!regex.test(email)) {
249
+ throw new ValidationError('Invalid email format')
250
+ }
251
+ return email.trim().toLowerCase()
252
+ },
253
+
254
+ password(password) {
255
+ if (password.length < 8) {
256
+ throw new ValidationError('Password must be at least 8 characters')
257
+ }
258
+ // Add more rules as needed
259
+ return password
260
+ },
261
+
262
+ sanitizeInput(input) {
263
+ // Remove potentially dangerous characters
264
+ return input
265
+ .replace(/[<>\"']/g, '')
266
+ .trim()
267
+ }
268
+ }
269
+ ```
270
+
271
+ ## OWASP Mobile Security Checklist
272
+
273
+ | Category | Check | Implementation |
274
+ | -------------------- | --------------------------- | ---------------------------------- |
275
+ | **Data Storage** | Credentials stored securely | Keychain/KeyStore for tokens |
276
+ | **Data Storage** | Sensitive data encrypted | AES-256 for cached data |
277
+ | **Communication** | HTTPS only | `validatesSecureCertificate: true` |
278
+ | **Communication** | Certificate pinning | SSL pinning enabled |
279
+ | **Authentication** | Token refresh | Auto-refresh before expiry |
280
+ | **Authentication** | Session timeout | Auto-logout after inactivity |
281
+ | **Input Validation** | Server-side validation | Never trust client input |
282
+ | **Input Validation** | Sanitize user input | Remove XSS patterns |
283
+ | **Cryptography** | No hardcoded keys | Keys from secure storage |
284
+ | **Cryptography** | Use standard algorithms | AES-256, SHA-256 |
285
+
286
+ ## Biometric Authentication
287
+
288
+ ### Using ti.identity Module
289
+
290
+ ```javascript
291
+ // lib/services/biometricService.js
292
+ const Identity = require('ti.identity')
293
+
294
+ exports.BiometricService = {
295
+ /**
296
+ * Check if biometrics are available
297
+ * @returns {{available: boolean, type: string|null, error: string|null}}
298
+ */
299
+ checkAvailability() {
300
+ if (!Identity.isSupported()) {
301
+ return { available: false, type: null, error: 'Biometrics not supported' }
302
+ }
303
+
304
+ const authResult = Identity.deviceCanAuthenticate()
305
+
306
+ if (authResult !== Identity.SUCCESS) {
307
+ const errors = {
308
+ [Identity.ERROR_TOUCH_ID_NOT_AVAILABLE]: 'Biometrics not available',
309
+ [Identity.ERROR_TOUCH_ID_NOT_ENROLLED]: 'No biometrics enrolled',
310
+ [Identity.ERROR_PASSCODE_NOT_SET]: 'Device passcode not set'
311
+ }
312
+
313
+ return {
314
+ available: false,
315
+ type: null,
316
+ error: errors[authResult] || 'Unknown error'
317
+ }
318
+ }
319
+
320
+ // Determine biometric type
321
+ const biometricType = OS_IOS
322
+ ? (Identity.biometryType === Identity.BIOMETRY_TYPE_FACE_ID ? 'Face ID' : 'Touch ID')
323
+ : 'Fingerprint'
324
+
325
+ return { available: true, type: biometricType, error: null }
326
+ },
327
+
328
+ /**
329
+ * Authenticate user with biometrics
330
+ * @param {string} reason - Reason shown to user
331
+ * @returns {Promise<boolean>}
332
+ */
333
+ authenticate(reason = L('biometric_reason')) {
334
+ return new Promise((resolve, reject) => {
335
+ const { available, error } = this.checkAvailability()
336
+
337
+ if (!available) {
338
+ return reject(new Error(error))
339
+ }
340
+
341
+ Identity.authenticate({
342
+ reason: reason,
343
+ allowableReuseDuration: 0, // Always require fresh auth
344
+ fallbackTitle: L('use_passcode'), // iOS fallback button
345
+ cancelTitle: L('cancel'),
346
+
347
+ callback: (e) => {
348
+ if (e.success) {
349
+ resolve(true)
350
+ } else {
351
+ const errorMsg = e.error || 'Authentication failed'
352
+ reject(new Error(errorMsg))
353
+ }
354
+ }
355
+ })
356
+ })
357
+ },
358
+
359
+ /**
360
+ * Get user-friendly name for the biometric type
361
+ */
362
+ getBiometricName() {
363
+ const { available, type } = this.checkAvailability()
364
+ return available ? type : null
365
+ }
366
+ }
367
+ ```
368
+
369
+ ### Biometric Login Flow
370
+
371
+ ```javascript
372
+ // controllers/auth/login.js
373
+ const { BiometricService } = require('lib/services/biometricService')
374
+ const { TokenStorage } = require('lib/services/tokenStorage')
375
+ const { AuthService } = require('lib/services/authService')
376
+
377
+ function init() {
378
+ // Check if biometric login is available and enabled
379
+ const { available, type } = BiometricService.checkAvailability()
380
+ const biometricEnabled = Ti.App.Properties.getBool('biometricEnabled', false)
381
+ const hasStoredCredentials = TokenStorage.hasRefreshToken()
382
+
383
+ if (available && biometricEnabled && hasStoredCredentials) {
384
+ // Show biometric login option
385
+ $.biometricBtn.applyProperties({
386
+ visible: true,
387
+ title: String.format(L('login_with'), type)
388
+ })
389
+ }
390
+ }
391
+
392
+ async function onBiometricLogin() {
393
+ try {
394
+ // Authenticate with biometrics
395
+ await BiometricService.authenticate(L('unlock_app'))
396
+
397
+ // Refresh token using stored refresh token
398
+ const user = await AuthService.refreshSession()
399
+
400
+ // Navigate to main app
401
+ Navigation.replace('main')
402
+
403
+ } catch (error) {
404
+ // Biometric failed - show password login
405
+ showMessage(L('biometric_failed'))
406
+ }
407
+ }
408
+
409
+ // Enable biometrics after successful password login
410
+ async function onLoginSuccess(user) {
411
+ const { available, type } = BiometricService.checkAvailability()
412
+
413
+ if (available && !Ti.App.Properties.getBool('askedBiometric', false)) {
414
+ Ti.App.Properties.setBool('askedBiometric', true)
415
+
416
+ // Ask user if they want to enable biometric login
417
+ const dialog = Ti.UI.createAlertDialog({
418
+ title: String.format(L('enable_biometric_title'), type),
419
+ message: String.format(L('enable_biometric_msg'), type),
420
+ buttonNames: [L('not_now'), L('enable')]
421
+ })
422
+
423
+ dialog.addEventListener('click', (e) => {
424
+ if (e.index === 1) {
425
+ Ti.App.Properties.setBool('biometricEnabled', true)
426
+ }
427
+ })
428
+
429
+ dialog.show()
430
+ }
431
+ }
432
+ ```
433
+
434
+ ## Deep Link Security
435
+
436
+ ### Validating Deep Links
437
+
438
+ ```javascript
439
+ // lib/services/deepLinkService.js
440
+ const logger = require('lib/services/logger')
441
+
442
+ // Whitelist of allowed schemes and hosts
443
+ const ALLOWED_SCHEMES = ['myapp', 'https']
444
+ const ALLOWED_HOSTS = ['myapp.com', 'www.myapp.com']
445
+
446
+ // Route patterns with their required auth levels
447
+ const ROUTES = {
448
+ '/product/:id': { auth: false, handler: 'openProduct' },
449
+ '/order/:id': { auth: true, handler: 'openOrder' },
450
+ '/profile': { auth: true, handler: 'openProfile' },
451
+ '/verify-email': { auth: false, handler: 'verifyEmail' }
452
+ }
453
+
454
+ exports.DeepLinkService = {
455
+ /**
456
+ * Parse and validate a deep link URL
457
+ * @param {string} url - The deep link URL
458
+ * @returns {{valid: boolean, route: string, params: object, error: string}}
459
+ */
460
+ parseUrl(url) {
461
+ try {
462
+ const parsed = this._parseUrlComponents(url)
463
+
464
+ // Validate scheme
465
+ if (!ALLOWED_SCHEMES.includes(parsed.scheme)) {
466
+ logger.warn('DeepLink', 'Invalid scheme', { url, scheme: parsed.scheme })
467
+ return { valid: false, error: 'Invalid URL scheme' }
468
+ }
469
+
470
+ // Validate host for https URLs
471
+ if (parsed.scheme === 'https' && !ALLOWED_HOSTS.includes(parsed.host)) {
472
+ logger.warn('DeepLink', 'Invalid host', { url, host: parsed.host })
473
+ return { valid: false, error: 'Invalid host' }
474
+ }
475
+
476
+ // Match route
477
+ const routeMatch = this._matchRoute(parsed.path)
478
+
479
+ if (!routeMatch) {
480
+ logger.warn('DeepLink', 'Unknown route', { url, path: parsed.path })
481
+ return { valid: false, error: 'Unknown route' }
482
+ }
483
+
484
+ // Sanitize parameters
485
+ const params = this._sanitizeParams({
486
+ ...routeMatch.params,
487
+ ...parsed.queryParams
488
+ })
489
+
490
+ return {
491
+ valid: true,
492
+ route: routeMatch.route,
493
+ handler: routeMatch.handler,
494
+ requiresAuth: routeMatch.requiresAuth,
495
+ params
496
+ }
497
+
498
+ } catch (error) {
499
+ logger.error('DeepLink', 'Parse error', { url, error: error.message })
500
+ return { valid: false, error: 'Invalid URL format' }
501
+ }
502
+ },
503
+
504
+ /**
505
+ * Handle an incoming deep link
506
+ */
507
+ async handle(url) {
508
+ const result = this.parseUrl(url)
509
+
510
+ if (!result.valid) {
511
+ return false
512
+ }
513
+
514
+ // Check auth requirement
515
+ if (result.requiresAuth && !AuthService.isAuthenticated()) {
516
+ // Store deep link for after login
517
+ Ti.App.Properties.setString('pendingDeepLink', url)
518
+ Navigation.open('login')
519
+ return true
520
+ }
521
+
522
+ // Execute handler
523
+ return this._executeHandler(result.handler, result.params)
524
+ },
525
+
526
+ _parseUrlComponents(url) {
527
+ // Custom parsing to handle both custom schemes and https
528
+ const schemeMatch = url.match(/^([a-z]+):\/\//)
529
+ const scheme = schemeMatch ? schemeMatch[1] : 'https'
530
+
531
+ const withoutScheme = url.replace(/^[a-z]+:\/\//, '')
532
+ const [hostPath, queryString] = withoutScheme.split('?')
533
+ const [host, ...pathParts] = hostPath.split('/')
534
+ const path = '/' + pathParts.join('/')
535
+
536
+ const queryParams = {}
537
+ if (queryString) {
538
+ queryString.split('&').forEach(pair => {
539
+ const [key, value] = pair.split('=')
540
+ queryParams[decodeURIComponent(key)] = decodeURIComponent(value || '')
541
+ })
542
+ }
543
+
544
+ return { scheme, host, path, queryParams }
545
+ },
546
+
547
+ _matchRoute(path) {
548
+ for (const [pattern, config] of Object.entries(ROUTES)) {
549
+ const params = this._extractParams(pattern, path)
550
+ if (params) {
551
+ return {
552
+ route: pattern,
553
+ handler: config.handler,
554
+ requiresAuth: config.auth,
555
+ params
556
+ }
557
+ }
558
+ }
559
+ return null
560
+ },
561
+
562
+ _extractParams(pattern, path) {
563
+ const patternParts = pattern.split('/')
564
+ const pathParts = path.split('/')
565
+
566
+ if (patternParts.length !== pathParts.length) return null
567
+
568
+ const params = {}
569
+
570
+ for (let i = 0; i < patternParts.length; i++) {
571
+ if (patternParts[i].startsWith(':')) {
572
+ const paramName = patternParts[i].slice(1)
573
+ params[paramName] = pathParts[i]
574
+ } else if (patternParts[i] !== pathParts[i]) {
575
+ return null
576
+ }
577
+ }
578
+
579
+ return params
580
+ },
581
+
582
+ _sanitizeParams(params) {
583
+ const sanitized = {}
584
+
585
+ for (const [key, value] of Object.entries(params)) {
586
+ // Remove potentially dangerous characters
587
+ const cleanKey = key.replace(/[<>"'&]/g, '')
588
+ const cleanValue = String(value).replace(/[<>"'&]/g, '').slice(0, 1000)
589
+ sanitized[cleanKey] = cleanValue
590
+ }
591
+
592
+ return sanitized
593
+ }
594
+ }
595
+ ```
596
+
597
+ ### Registering Deep Link Handler
598
+
599
+ ```javascript
600
+ // alloy.js
601
+ const { DeepLinkService } = require('lib/services/deepLinkService')
602
+
603
+ // iOS: Handle app launch from deep link
604
+ if (Ti.App.getArguments().url) {
605
+ DeepLinkService.handle(Ti.App.getArguments().url)
606
+ }
607
+
608
+ // Handle deep links while app is running
609
+ Ti.App.addEventListener('resumed', (e) => {
610
+ if (e.url) {
611
+ DeepLinkService.handle(e.url)
612
+ }
613
+ })
614
+
615
+ // Android: Handle intent
616
+ if (OS_ANDROID) {
617
+ const activity = Ti.Android.currentActivity
618
+ const intent = activity.intent
619
+
620
+ if (intent && intent.data) {
621
+ DeepLinkService.handle(intent.data)
622
+ }
623
+
624
+ activity.addEventListener('newintent', (e) => {
625
+ if (e.intent && e.intent.data) {
626
+ DeepLinkService.handle(e.intent.data)
627
+ }
628
+ })
629
+ }
630
+ ```
631
+
632
+ ## Jailbreak/Root Detection
633
+
634
+ ### Detection Service
635
+
636
+ ```javascript
637
+ // lib/services/securityService.js
638
+ const logger = require('lib/services/logger')
639
+
640
+ exports.SecurityService = {
641
+ /**
642
+ * Check if device is jailbroken (iOS) or rooted (Android)
643
+ * @returns {{compromised: boolean, reasons: string[]}}
644
+ */
645
+ checkDeviceIntegrity() {
646
+ const reasons = []
647
+
648
+ if (OS_IOS) {
649
+ reasons.push(...this._checkiOSJailbreak())
650
+ } else {
651
+ reasons.push(...this._checkAndroidRoot())
652
+ }
653
+
654
+ if (reasons.length > 0) {
655
+ logger.warn('Security', 'Device integrity check failed', { reasons })
656
+ }
657
+
658
+ return {
659
+ compromised: reasons.length > 0,
660
+ reasons
661
+ }
662
+ },
663
+
664
+ _checkiOSJailbreak() {
665
+ const reasons = []
666
+ const file = Ti.Filesystem.getFile
667
+
668
+ // Check for common jailbreak files
669
+ const jailbreakPaths = [
670
+ '/Applications/Cydia.app',
671
+ '/Library/MobileSubstrate/MobileSubstrate.dylib',
672
+ '/bin/bash',
673
+ '/usr/sbin/sshd',
674
+ '/etc/apt',
675
+ '/private/var/lib/apt/',
676
+ '/usr/bin/ssh'
677
+ ]
678
+
679
+ jailbreakPaths.forEach(path => {
680
+ try {
681
+ if (file(path).exists()) {
682
+ reasons.push(`Jailbreak file found: ${path}`)
683
+ }
684
+ } catch (e) {
685
+ // File access error might indicate sandbox bypass attempt
686
+ }
687
+ })
688
+
689
+ // Check if we can write outside sandbox
690
+ try {
691
+ const testFile = file('/private/jailbreak_test')
692
+ testFile.write('test')
693
+ testFile.deleteFile()
694
+ reasons.push('Sandbox bypass detected')
695
+ } catch (e) {
696
+ // Expected - sandbox is working
697
+ }
698
+
699
+ return reasons
700
+ },
701
+
702
+ _checkAndroidRoot() {
703
+ const reasons = []
704
+ const file = Ti.Filesystem.getFile
705
+
706
+ // Check for su binary
707
+ const suPaths = [
708
+ '/system/app/Superuser.apk',
709
+ '/sbin/su',
710
+ '/system/bin/su',
711
+ '/system/xbin/su',
712
+ '/data/local/xbin/su',
713
+ '/data/local/bin/su',
714
+ '/system/sd/xbin/su',
715
+ '/system/bin/failsafe/su',
716
+ '/data/local/su'
717
+ ]
718
+
719
+ suPaths.forEach(path => {
720
+ try {
721
+ if (file(path).exists()) {
722
+ reasons.push(`Root binary found: ${path}`)
723
+ }
724
+ } catch (e) {
725
+ // Ignore access errors
726
+ }
727
+ })
728
+
729
+ // Check for root management apps
730
+ const rootApps = [
731
+ 'com.topjohnwu.magisk',
732
+ 'com.koushikdutta.superuser',
733
+ 'com.noshufou.android.su',
734
+ 'eu.chainfire.supersu'
735
+ ]
736
+
737
+ // Note: Checking installed packages requires additional permissions
738
+ // This is a simplified check
739
+
740
+ // Check build tags
741
+ const buildTags = Ti.Platform.model
742
+ if (buildTags && buildTags.includes('test-keys')) {
743
+ reasons.push('Test build detected')
744
+ }
745
+
746
+ return reasons
747
+ },
748
+
749
+ /**
750
+ * Enforce security policy based on device integrity
751
+ */
752
+ enforceSecurityPolicy() {
753
+ const { compromised, reasons } = this.checkDeviceIntegrity()
754
+
755
+ if (!compromised) return true
756
+
757
+ // Log security event
758
+ logger.error('Security', 'Compromised device detected', {
759
+ reasons,
760
+ platform: Ti.Platform.osname,
761
+ model: Ti.Platform.model
762
+ })
763
+
764
+ // Get configured policy
765
+ const policy = Alloy.CFG.securityPolicy || 'warn'
766
+
767
+ switch (policy) {
768
+ case 'block':
769
+ // Prevent app usage
770
+ this._showBlockedScreen()
771
+ return false
772
+
773
+ case 'restrict':
774
+ // Disable sensitive features
775
+ Ti.App.Properties.setBool('restrictedMode', true)
776
+ this._showWarning()
777
+ return true
778
+
779
+ case 'warn':
780
+ default:
781
+ // Just warn the user
782
+ this._showWarning()
783
+ return true
784
+ }
785
+ },
786
+
787
+ _showBlockedScreen() {
788
+ const dialog = Ti.UI.createAlertDialog({
789
+ title: L('security_blocked_title'),
790
+ message: L('security_blocked_msg'),
791
+ buttonNames: [L('close')]
792
+ })
793
+
794
+ dialog.addEventListener('click', () => {
795
+ // Close the app (iOS) or minimize (Android)
796
+ if (OS_IOS) {
797
+ Ti.Platform.openURL('prefs:root=General')
798
+ }
799
+ })
800
+
801
+ dialog.show()
802
+ },
803
+
804
+ _showWarning() {
805
+ Ti.UI.createAlertDialog({
806
+ title: L('security_warning_title'),
807
+ message: L('security_warning_msg'),
808
+ buttonNames: [L('i_understand')]
809
+ }).show()
810
+ }
811
+ }
812
+ ```
813
+
814
+ ### Integrating Security Checks
815
+
816
+ ```javascript
817
+ // alloy.js
818
+ const { SecurityService } = require('lib/services/securityService')
819
+
820
+ // Check device integrity at app start
821
+ const securityCheck = SecurityService.enforceSecurityPolicy()
822
+
823
+ if (!securityCheck) {
824
+ // Don't initialize the app
825
+ return
826
+ }
827
+
828
+ // Check again periodically (in case of runtime jailbreak tools)
829
+ setInterval(() => {
830
+ SecurityService.checkDeviceIntegrity()
831
+ }, 5 * 60 * 1000) // Every 5 minutes
832
+ ```
833
+
834
+ ## Additional Security Checklist
835
+
836
+ | Category | Check | Implementation |
837
+ | -------------- | -------------------------- | ------------------------ |
838
+ | **Biometrics** | Use ti.identity for auth | BiometricService wrapper |
839
+ | **Biometrics** | Never store biometric data | System handles storage |
840
+ | **Biometrics** | Fallback to password | Always offer alternative |
841
+ | **Deep Links** | Whitelist allowed schemes | ALLOWED_SCHEMES constant |
842
+ | **Deep Links** | Whitelist allowed hosts | ALLOWED_HOSTS constant |
843
+ | **Deep Links** | Sanitize all parameters | _sanitizeParams() |
844
+ | **Deep Links** | Check auth requirements | requiresAuth per route |
845
+ | **Integrity** | Check for jailbreak/root | checkDeviceIntegrity() |
846
+ | **Integrity** | Define security policy | block/restrict/warn |
847
+ | **Integrity** | Log security events | Always log compromises |