@maccesar/titools 2.2.12 → 2.3.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 (98) hide show
  1. package/README.md +255 -713
  2. package/bin/titools.js +1 -1
  3. package/lib/commands/update.js +5 -6
  4. package/lib/config.js +1 -0
  5. package/lib/downloader.js +10 -0
  6. package/package.json +1 -1
  7. package/skills/alloy-guides/SKILL.md +46 -48
  8. package/skills/alloy-guides/references/CONTROLLERS.md +4 -6
  9. package/skills/alloy-guides/references/MODELS.md +340 -184
  10. package/skills/alloy-guides/references/VIEWS_DYNAMIC.md +3 -4
  11. package/skills/alloy-guides/references/VIEWS_STYLES.md +44 -46
  12. package/skills/alloy-guides/references/VIEWS_WITHOUT_CONTROLLERS.md +4 -5
  13. package/skills/alloy-guides/references/VIEWS_XML.md +51 -17
  14. package/skills/alloy-guides/references/WIDGETS.md +1 -1
  15. package/skills/alloy-howtos/SKILL.md +12 -13
  16. package/skills/alloy-howtos/references/cli_reference.md +40 -41
  17. package/skills/alloy-howtos/references/samples.md +3 -4
  18. package/skills/purgetss/SKILL.md +369 -356
  19. package/skills/purgetss/references/EXAMPLES.md +24 -25
  20. package/skills/purgetss/references/animation-advanced.md +555 -0
  21. package/skills/purgetss/references/animation-system.md +395 -995
  22. package/skills/purgetss/references/apply-directive.md +111 -141
  23. package/skills/purgetss/references/arbitrary-values.md +206 -480
  24. package/skills/purgetss/references/class-categories.md +277 -0
  25. package/skills/purgetss/references/class-index.md +1 -420
  26. package/skills/purgetss/references/cli-commands.md +446 -556
  27. package/skills/purgetss/references/configurable-properties.md +163 -599
  28. package/skills/purgetss/references/custom-rules.md +33 -76
  29. package/skills/purgetss/references/customization-deep-dive.md +319 -518
  30. package/skills/purgetss/references/dynamic-component-creation.md +33 -37
  31. package/skills/purgetss/references/grid-layout.md +42 -371
  32. package/skills/purgetss/references/icon-fonts.md +82 -475
  33. package/skills/purgetss/references/installation-setup.md +159 -331
  34. package/skills/purgetss/references/migration-guide.md +54 -109
  35. package/skills/purgetss/references/opacity-modifier.md +25 -222
  36. package/skills/purgetss/references/performance-tips.md +2 -2
  37. package/skills/purgetss/references/platform-modifiers.md +21 -407
  38. package/skills/purgetss/references/tikit-components.md +237 -104
  39. package/skills/purgetss/references/titanium-resets.md +20 -21
  40. package/skills/purgetss/references/ui-ux-design.md +171 -1702
  41. package/skills/ti-api/SKILL.md +109 -0
  42. package/skills/ti-api/references/api-android.md +675 -0
  43. package/skills/ti-api/references/api-app-platform.md +636 -0
  44. package/skills/ti-api/references/api-core.md +764 -0
  45. package/skills/ti-api/references/api-data-network.md +641 -0
  46. package/skills/ti-api/references/api-media.md +655 -0
  47. package/skills/ti-api/references/api-modules-ble-bluetooth.md +657 -0
  48. package/skills/ti-api/references/api-modules-coremotion-urlsession.md +411 -0
  49. package/skills/ti-api/references/api-modules-map.md +632 -0
  50. package/skills/ti-api/references/api-modules-nfc.md +725 -0
  51. package/skills/ti-api/references/api-modules-social-misc.md +526 -0
  52. package/skills/ti-api/references/api-services.md +700 -0
  53. package/skills/ti-api/references/api-ui-android.md +499 -0
  54. package/skills/ti-api/references/api-ui-extras.md +702 -0
  55. package/skills/ti-api/references/api-ui-ios-animator.md +378 -0
  56. package/skills/ti-api/references/api-ui-ios.md +756 -0
  57. package/skills/ti-api/references/api-ui-lists.md +581 -0
  58. package/skills/ti-api/references/api-ui-text-input.md +607 -0
  59. package/skills/ti-api/references/api-ui-views.md +572 -0
  60. package/skills/ti-api/references/api-ui-windows-navigation.md +676 -0
  61. package/skills/ti-api/references/api-xml-global.md +743 -0
  62. package/skills/ti-expert/SKILL.md +46 -45
  63. package/skills/ti-expert/references/adaptive-layouts.md +414 -0
  64. package/skills/ti-expert/references/alloy-builtins.md +16 -20
  65. package/skills/ti-expert/references/alloy-structure.md +40 -42
  66. package/skills/ti-expert/references/anti-patterns.md +34 -0
  67. package/skills/ti-expert/references/code-conventions.md +5 -3
  68. package/skills/ti-expert/references/error-handling.md +14 -7
  69. package/skills/ti-expert/references/examples.md +4 -2
  70. package/skills/ti-expert/references/performance-optimization.md +26 -24
  71. package/skills/ti-expert/references/security-device.md +17 -14
  72. package/skills/ti-expert/references/security-fundamentals.md +60 -101
  73. package/skills/ti-expert/references/state-management.md +15 -14
  74. package/skills/ti-expert/references/testing-e2e-ci.md +30 -21
  75. package/skills/ti-expert/references/theming.md +12 -20
  76. package/skills/ti-guides/SKILL.md +13 -17
  77. package/skills/ti-guides/references/advanced-data-and-images.md +30 -10
  78. package/skills/ti-guides/references/application-frameworks.md +3 -3
  79. package/skills/ti-guides/references/coding-best-practices.md +31 -2
  80. package/skills/ti-guides/references/commonjs-advanced.md +46 -4
  81. package/skills/ti-guides/references/hello-world.md +9 -13
  82. package/skills/ti-guides/references/hyperloop-native-access.md +4 -1
  83. package/skills/ti-guides/references/javascript-primer.md +13 -5
  84. package/skills/ti-guides/references/resources.md +0 -2
  85. package/skills/ti-guides/references/style-and-conventions.md +1 -0
  86. package/skills/ti-guides/references/tiapp-config.md +0 -32
  87. package/skills/ti-howtos/SKILL.md +43 -45
  88. package/skills/ti-howtos/references/buffer-codec-streams.md +25 -3
  89. package/skills/ti-howtos/references/debugging-profiling.md +14 -25
  90. package/skills/ti-howtos/references/google-maps-v2.md +3 -3
  91. package/skills/ti-howtos/references/ios-map-kit.md +8 -1
  92. package/skills/ti-howtos/references/notification-services.md +0 -1
  93. package/skills/ti-howtos/references/webpack-build-pipeline.md +3 -0
  94. package/skills/ti-ui/SKILL.md +47 -49
  95. package/skills/ti-ui/references/application-structures.md +3 -2
  96. package/skills/ti-ui/references/orientation.md +17 -9
  97. package/skills/ti-ui/references/platform-ui-ios.md +63 -0
  98. package/skills/ti-ui/references/scrolling-views.md +39 -0
@@ -66,45 +66,44 @@ app/
66
66
 
67
67
  ## lib/ folder and module require paths
68
68
 
69
- :::danger CRITICAL: lib/ Folder is FLATTENED During Build
70
- When Alloy compiles, the **entire `lib/` folder is flattened to the root of Resources**. This means:
71
- - `app/lib/services/authService.js` → `Resources/iphone/services/authService.js`
72
- - `app/lib/api/authApi.js` → `Resources/iphone/api/authApi.js`
73
-
74
- **Therefore, require statements should NOT include `lib/` prefix:**
75
- ```javascript
76
- // ❌ WRONG - Will fail at runtime
77
- const authApi = require('lib/api/authApi')
78
-
79
- // ✅ CORRECT - Path relative to flattened lib/
80
- const authApi = require('api/authApi')
81
- const authService = require('services/authService')
82
- ```
83
-
84
- **This applies to:**
85
- - All files in `app/lib/` (services, api, helpers, etc.)
86
- - Cross-references within lib/ files
87
- - Controller requires of lib/ files
88
-
89
- **Example project structure:**
90
- ```
91
- app/
92
- ├── lib/
93
- │ ├── services/
94
- │ │ ├── authService.js # require('services/navigationService')
95
- │ │ ├── navigationService.js # require('services/notificationService')
96
- │ │ └── notificationService.js
97
- │ ├── api/
98
- │ │ ├── authApi.js # require('services/authService')
99
- │ │ ├── userApi.js
100
- │ │ └── frameApi.js
101
- │ └── repositories/
102
- │ ├── userRepository.js
103
- │ └── settingsRepository.js
104
- ├── controllers/
105
- │ └── index.js # require('services/authService')
106
- ```
107
- :::
69
+ > **🚨 CRITICAL: lib/ Folder is FLATTENED During Build**
70
+ > When Alloy compiles, the **entire `lib/` folder is flattened to the root of Resources**. This means:
71
+ > - `app/lib/services/authService.js` → `Resources/iphone/services/authService.js`
72
+ > - `app/lib/api/authApi.js` → `Resources/iphone/api/authApi.js`
73
+ >
74
+ > **Therefore, require statements should NOT include `lib/` prefix:**
75
+ > ```javascript
76
+ > // ❌ WRONG - Will fail at runtime
77
+ > const authApi = require('lib/api/authApi')
78
+ >
79
+ > // ✅ CORRECT - Path relative to flattened lib/
80
+ > const authApi = require('api/authApi')
81
+ > const authService = require('services/authService')
82
+ > ```
83
+ >
84
+ > **This applies to:**
85
+ > - All files in `app/lib/` (services, api, helpers, etc.)
86
+ > - Cross-references within lib/ files
87
+ > - Controller requires of lib/ files
88
+ >
89
+ > **Example project structure:**
90
+ > ```
91
+ > app/
92
+ > ├── lib/
93
+ > │ ├── services/
94
+ > │ │ ├── authService.js # require('services/navigationService')
95
+ > │ │ ├── navigationService.js # require('services/notificationService')
96
+ > │ │ └── notificationService.js
97
+ > │ ├── api/
98
+ > │ │ ├── authApi.js # require('services/authService')
99
+ > │ │ ├── userApi.js
100
+ > │ │ └── frameApi.js
101
+ > │ └── repositories/
102
+ > │ ├── userRepository.js
103
+ > │ └── settingsRepository.js
104
+ > ├── controllers/
105
+ > │ └── index.js # require('services/authService')
106
+ > ```
108
107
 
109
108
  ## Data layer: two approaches
110
109
 
@@ -338,9 +337,8 @@ const loadData = () => {
338
337
  }
339
338
  ```
340
339
 
341
- :::tip Widget Styles
342
- Widgets have their own `styles/widget.tss` file. Define all widget-specific styles there to keep them self-contained and portable.
343
- :::
340
+ > **💡 Widget Styles**
341
+ > Widgets have their own `styles/widget.tss` file. Define all widget-specific styles there to keep them self-contained and portable.
344
342
 
345
343
  ### Widget ↔ controller communication
346
344
 
@@ -76,6 +76,39 @@
76
76
  - iOS: Use `Ti.UI.iOS.createDocumentViewer` for files, or simple `Ti.UI.createOptionDialog` + `Ti.UI.Clipboard` for links
77
77
  - Android: Use `Ti.Android.createIntent` with ACTION_SEND
78
78
 
79
+ ## Community-Discovered Patterns
80
+
81
+ ### 15. Using extendEdges without autoAdjustScrollViewInsets (iOS)
82
+
83
+ **Anti-pattern:** Setting `extendEdges: [Ti.UI.EXTEND_EDGE_ALL]` on a Window without also setting `autoAdjustScrollViewInsets: true`.
84
+
85
+ ```javascript
86
+ // Wrong: content overlaps behind navigation bar
87
+ const win = Ti.UI.createWindow({
88
+ title: 'My Screen',
89
+ largeTitleEnabled: true,
90
+ extendEdges: [Ti.UI.EXTEND_EDGE_ALL]
91
+ // missing autoAdjustScrollViewInsets!
92
+ });
93
+ ```
94
+
95
+ **Why it's wrong:** `extendEdges` tells iOS to extend the view's content behind the navigation bar and tab bar. Without `autoAdjustScrollViewInsets: true`, iOS does not adjust the ScrollView's content insets, so the scroll content starts at y=0 — directly behind the navigation bar.
96
+
97
+ **Fix:** Always pair `extendEdges` with `autoAdjustScrollViewInsets`:
98
+
99
+ ```javascript
100
+ const win = Ti.UI.createWindow({
101
+ title: 'My Screen',
102
+ largeTitleEnabled: true,
103
+ extendEdges: [Ti.UI.EXTEND_EDGE_ALL],
104
+ autoAdjustScrollViewInsets: true
105
+ });
106
+ ```
107
+
108
+ These three properties work together: `extendEdges` creates the blur/translucent effect, `autoAdjustScrollViewInsets` prevents content overlap, and `largeTitleEnabled` shows the collapsible large title. Without `extendEdges`, the large title also renders with a visible delay (empty nav bar gap appears first, then the title draws).
109
+
110
+ This applies to Windows inside both standalone NavigationWindow and TabGroup (which wraps each Tab in an implicit NavigationWindow on iOS).
111
+
79
112
  ---
80
113
 
81
114
  ## Quick reference table
@@ -88,3 +121,4 @@
88
121
  | `lib/` prefix | lib/ is flattened | Use path without lib/ |
89
122
  | `$.index.open()` | Wrong ID reference | Use actual Window ID |
90
123
  | `createNotification` | API doesn't exist | `createAlertDialog` |
124
+ | `extendEdges` alone | Content behind nav bar | Add `autoAdjustScrollViewInsets: true` |
@@ -386,13 +386,13 @@ const Backbone = require('alloy/backbone')
386
386
 
387
387
  const EventBus = _.clone(Backbone.Events)
388
388
 
389
- // Named export for event constants
390
- exports.Events = {
389
+ // Attach event constants directly to the bus object so they survive module.exports
390
+ EventBus.Events = {
391
391
  USER_UPDATED: 'user:updated',
392
392
  SYNC_COMPLETE: 'sync:complete'
393
393
  }
394
394
 
395
- // Default export for the bus itself
395
+ // Export the bus (EventBus.Events is accessible as a property)
396
396
  module.exports = EventBus
397
397
 
398
398
  // Usage
@@ -400,6 +400,8 @@ const EventBus = require('services/eventBus')
400
400
  const { Events } = EventBus
401
401
  ```
402
402
 
403
+ > Note: setting `exports.Events = {...}` before `module.exports = EventBus` has no effect — the `module.exports` assignment discards the prior `exports` object. Attach constants directly to the object being exported instead.
404
+
403
405
  ### Re-exports (Barrel Files)
404
406
  ```javascript
405
407
  // lib/api/index.js - Barrel file
@@ -4,7 +4,10 @@
4
4
 
5
5
  ```javascript
6
6
  // lib/core/appError.js
7
- module.exports = class AppError extends Error {
7
+ // IMPORTANT: Use named exports (exports.Foo), NOT module.exports = class Foo.
8
+ // Multiple `module.exports =` assignments overwrite each other — only the last one survives.
9
+
10
+ class AppError extends Error {
8
11
  constructor(message, code, statusCode = 500) {
9
12
  super(message)
10
13
  this.code = code
@@ -14,32 +17,36 @@ module.exports = class AppError extends Error {
14
17
  }
15
18
  }
16
19
 
17
- // Specific error types
18
- module.exports = class NetworkError extends AppError {
20
+ class NetworkError extends AppError {
19
21
  constructor(message = 'Network request failed') {
20
22
  super(message, 'NETWORK_ERROR', 0)
21
23
  }
22
24
  }
23
25
 
24
- module.exports = class AuthError extends AppError {
26
+ class AuthError extends AppError {
25
27
  constructor(message = 'Authentication failed') {
26
28
  super(message, 'AUTH_ERROR', 401)
27
29
  }
28
30
  }
29
31
 
30
- module.exports = class ValidationError extends AppError {
32
+ class ValidationError extends AppError {
31
33
  constructor(message = 'Validation failed') {
32
34
  super(message, 'VALIDATION_ERROR', 400)
33
35
  }
34
36
  }
35
37
 
36
- module.exports = class NotFoundError extends AppError {
38
+ class NotFoundError extends AppError {
37
39
  constructor(message = 'Resource not found') {
38
40
  super(message, 'NOT_FOUND', 404)
39
41
  }
40
42
  }
41
43
 
42
- // Error codes for reference
44
+ exports.AppError = AppError
45
+ exports.NetworkError = NetworkError
46
+ exports.AuthError = AuthError
47
+ exports.ValidationError = ValidationError
48
+ exports.NotFoundError = NotFoundError
49
+
43
50
  exports.ErrorCodes = {
44
51
  NETWORK_ERROR: 'NETWORK_ERROR',
45
52
  AUTH_ERROR: 'AUTH_ERROR',
@@ -736,9 +736,11 @@ function init() {
736
736
  }
737
737
 
738
738
  function onTabFocus(e) {
739
- // Analytics tracking
739
+ // Track tab changes via your analytics service
740
+ // Note: Ti.Analytics was removed from the Titanium SDK.
741
+ // Use a third-party analytics service (e.g., Firebase, Segment) instead.
740
742
  const tabNames = ['home', 'search', 'cart', 'profile']
741
- Ti.Analytics.featureEvent(`tab:${tabNames[e.index]}`)
743
+ Analytics.track(`tab:${tabNames[e.index]}`)
742
744
  }
743
745
 
744
746
  function onCartUpdated({ itemCount }) {
@@ -212,12 +212,14 @@ $.cleanup = cleanup
212
212
  ```javascript
213
213
  // lib/services/database.js
214
214
  exports.DB = {
215
- // Use transactions for multiple writes
215
+ // Use transactions to speed batch inserts
216
+ // Per official docs: use db.execute('BEGIN') / db.execute('COMMIT') — there are no
217
+ // dedicated begin_transaction() / commit() / rollback() methods on Ti.Database.DB.
216
218
  batchInsert(items) {
217
219
  const db = Ti.Database.open('mydb')
218
220
 
219
221
  try {
220
- db.begin_transaction()
222
+ db.execute('BEGIN')
221
223
 
222
224
  items.forEach(item => {
223
225
  db.execute(
@@ -228,12 +230,12 @@ exports.DB = {
228
230
  )
229
231
  })
230
232
 
231
- db.commit()
233
+ db.execute('COMMIT')
232
234
 
233
235
  console.log(`Inserted ${items.length} items`)
234
236
 
235
237
  } catch (e) {
236
- db.rollback()
238
+ db.execute('ROLLBACK')
237
239
  console.error('Batch insert failed', e)
238
240
  throw e
239
241
 
@@ -574,14 +576,14 @@ function cleanup() {
574
576
 
575
577
  ### Common use cases
576
578
 
577
- | Pattern | Use Case | Delay |
579
+ | Pattern | Use Case | Delay |
578
580
  | -------- | ---------------- | ------------ |
579
- | Debounce | Search input | 300ms |
580
- | Debounce | Auto-save | 1000ms |
581
- | Debounce | Window resize | 150ms |
582
- | Throttle | Scroll events | 50-100ms |
581
+ | Debounce | Search input | 300ms |
582
+ | Debounce | Auto-save | 1000ms |
583
+ | Debounce | Window resize | 150ms |
584
+ | Throttle | Scroll events | 50-100ms |
583
585
  | Throttle | Mouse/touch move | 16ms (60fps) |
584
- | Throttle | API polling | 5000ms+ |
586
+ | Throttle | API polling | 5000ms+ |
585
587
 
586
588
  ### Combined pattern for real-time + final
587
589
 
@@ -622,18 +624,18 @@ function cleanup() {
622
624
 
623
625
  ## Performance checklist
624
626
 
625
- | Area | Check |
627
+ | Area | Check |
626
628
  | ------------- | --------------------------------------- |
627
- | **Bridge** | Cached Ti.Platform properties |
628
- | **Bridge** | Using applyProperties for updates |
629
- | **Bridge** | TSS styles instead of inline attributes |
630
- | **Memory** | All global listeners cleaned up |
631
- | **Memory** | Heavy objects nulled in cleanup |
632
- | **Memory** | Images resized appropriately |
633
- | **Database** | Using transactions for batch ops |
634
- | **Database** | Indexes on frequently queried columns |
635
- | **Database** | ResultSets and DB handles closed |
636
- | **Animation** | Using native animations, not intervals |
637
- | **Animation** | GPU-accelerated properties preferred |
638
- | **Timing** | Debounce on search/input |
639
- | **Timing** | Throttle on scroll/touch events |
629
+ | **Bridge** | Cached Ti.Platform properties |
630
+ | **Bridge** | Using applyProperties for updates |
631
+ | **Bridge** | TSS styles instead of inline attributes |
632
+ | **Memory** | All global listeners cleaned up |
633
+ | **Memory** | Heavy objects nulled in cleanup |
634
+ | **Memory** | Images resized appropriately |
635
+ | **Database** | Using transactions for batch ops |
636
+ | **Database** | Indexes on frequently queried columns |
637
+ | **Database** | ResultSets and DB handles closed |
638
+ | **Animation** | Using native animations, not intervals |
639
+ | **Animation** | GPU-accelerated properties preferred |
640
+ | **Timing** | Debounce on search/input |
641
+ | **Timing** | Throttle on scroll/touch events |
@@ -502,6 +502,9 @@ exports.SecurityService = {
502
502
  },
503
503
 
504
504
  _showBlockedScreen() {
505
+ // Note: iOS guidelines prohibit programmatically terminating an app.
506
+ // The best practice is to show a blocking dialog and prevent further interaction.
507
+ // On Android you can call Ti.Android.currentActivity.finish() to close the activity.
505
508
  const dialog = Ti.UI.createAlertDialog({
506
509
  title: L('security_blocked_title'),
507
510
  message: L('security_blocked_msg'),
@@ -509,10 +512,10 @@ exports.SecurityService = {
509
512
  })
510
513
 
511
514
  dialog.addEventListener('click', () => {
512
- // Close the app (iOS) or minimize (Android)
513
- if (OS_IOS) {
514
- Ti.Platform.openURL('prefs:root=General')
515
+ if (OS_ANDROID) {
516
+ Ti.Android.currentActivity.finish()
515
517
  }
518
+ // On iOS: leave the dialog visible — Apple guidelines prohibit force-quitting apps.
516
519
  })
517
520
 
518
521
  dialog.show()
@@ -550,15 +553,15 @@ setInterval(() => {
550
553
 
551
554
  ## Security checklist
552
555
 
553
- | Category | Check | Implementation |
556
+ | Category | Check | Implementation |
554
557
  | -------------- | -------------------------- | ------------------------ |
555
- | **Biometrics** | Use ti.identity for auth | BiometricService wrapper |
556
- | **Biometrics** | Never store biometric data | System handles storage |
557
- | **Biometrics** | Fallback to password | Always offer alternative |
558
- | **Deep Links** | Whitelist allowed schemes | ALLOWED_SCHEMES constant |
559
- | **Deep Links** | Whitelist allowed hosts | ALLOWED_HOSTS constant |
560
- | **Deep Links** | Sanitize all parameters | _sanitizeParams() |
561
- | **Deep Links** | Check auth requirements | requiresAuth per route |
562
- | **Integrity** | Check for jailbreak/root | checkDeviceIntegrity() |
563
- | **Integrity** | Define security policy | block/restrict/warn |
564
- | **Integrity** | Log security events | Always log compromises |
558
+ | **Biometrics** | Use ti.identity for auth | BiometricService wrapper |
559
+ | **Biometrics** | Never store biometric data | System handles storage |
560
+ | **Biometrics** | Fallback to password | Always offer alternative |
561
+ | **Deep Links** | Whitelist allowed schemes | ALLOWED_SCHEMES constant |
562
+ | **Deep Links** | Whitelist allowed hosts | ALLOWED_HOSTS constant |
563
+ | **Deep Links** | Sanitize all parameters | _sanitizeParams() |
564
+ | **Deep Links** | Check auth requirements | requiresAuth per route |
565
+ | **Integrity** | Check for jailbreak/root | checkDeviceIntegrity() |
566
+ | **Integrity** | Define security policy | block/restrict/warn |
567
+ | **Integrity** | Log security events | Always log compromises |
@@ -4,107 +4,90 @@
4
4
 
5
5
  **NEVER store tokens in:** `Ti.App.Properties` (plaintext), localStorage, or files.
6
6
 
7
- **USE platform-specific secure storage:**
7
+ **USE the `ti.identity` module** — it handles iOS Keychain and Android Keystore through a unified API. Both platforms use `Identity.createKeychainItem()`.
8
8
 
9
9
  ```javascript
10
10
  // lib/services/tokenStorage.js
11
+ const Identity = require('ti.identity')
12
+
13
+ // Create a keychainItem once per identifier
14
+ function createItem(identifier) {
15
+ return Identity.createKeychainItem({ identifier })
16
+ }
17
+
11
18
  exports.TokenStorage = {
12
19
  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'
20
+ return new Promise((resolve, reject) => {
21
+ const item = createItem('authToken')
22
+ item.addEventListener('save', (e) => {
23
+ e.success ? resolve() : reject(new Error(e.error))
25
24
  })
26
- }
25
+ item.save(token)
26
+ })
27
27
  },
28
28
 
29
29
  get() {
30
- if (Ti.Platform.osname === 'android') {
31
- const keyStore = Ti.Android.createKeyStore({
32
- name: 'SecureKeyStore'
30
+ return new Promise((resolve, reject) => {
31
+ const item = createItem('authToken')
32
+ item.addEventListener('read', (e) => {
33
+ e.success ? resolve(e.value) : reject(new Error(e.error))
33
34
  })
34
- return keyStore.getEntry('authToken')
35
- } else {
36
- return Ti.KeychainItem.getItem({
37
- identifier: 'authToken',
38
- accessGroup: 'com.yourapp.keychain'
39
- })
40
- }
35
+ item.read()
36
+ })
41
37
  },
42
38
 
43
39
  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'
40
+ return new Promise((resolve, reject) => {
41
+ const item = createItem('authToken')
42
+ item.addEventListener('reset', (e) => {
43
+ e.success ? resolve() : reject(new Error(e.error))
53
44
  })
54
- }
45
+ item.reset()
46
+ })
55
47
  }
56
48
  }
57
49
  ```
58
50
 
51
+ > Verified against official `ti.identity` module docs. `Identity.createKeychainItem({identifier})` is the cross-platform API — it maps to iOS Keychain and Android Keystore automatically. `Ti.Android.createKeyStore()` and `Ti.KeychainItem.setItem()` do NOT exist.
52
+
59
53
  ## Certificate pinning
60
54
 
61
- Prevent man-in-the-middle attacks by pinning SSL certificates:
55
+ Prevent man-in-the-middle attacks by pinning SSL certificates using the **`ti.https` module** (community module — not built into the SDK):
62
56
 
63
57
  ```javascript
64
58
  // lib/api/pinnedClient.js
65
- exports.createPinnedClient = function() {
66
- const client = Ti.Network.createHTTPClient({
67
- // Security: Enable certificate pinning
68
- certificatePinning: true,
59
+ // Requires: ti.https module install via npm or tiapp.xml modules section
60
+ const HTTPS = require('ti.https')
61
+
62
+ const securityManager = HTTPS.createX509CertificatePinningSecurityManager([
63
+ {
64
+ url: 'https://api.example.com',
65
+ serverCertificate: Ti.Filesystem.getFile(
66
+ Ti.Filesystem.resourcesDirectory, 'certificates/api-pin.pem'
67
+ ).read()
68
+ }
69
+ ])
69
70
 
70
- // Specify allowed certificates
71
+ exports.createPinnedClient = function(options = {}) {
72
+ return Ti.Network.createHTTPClient({
73
+ securityManager, // must be set at creation time
71
74
  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
- }
75
+ timeout: 10000,
76
+ ...options
83
77
  })
84
-
85
- return client
86
78
  }
87
79
  ```
88
80
 
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
- ```
81
+ > `certificatePinning: true` is NOT a valid `HTTPClient` property. Certificate pinning requires the `ti.https` module and must be set via the `securityManager` property when creating the client. Note that the `validatesSecureCertificate` property of `HTTPClient` is not honored for pinned URLs — the security manager takes precedence.
102
82
 
103
83
  ## Data encryption at rest
104
84
 
85
+ > **Community pattern** — `ti.crypto` is a third-party community module (not part of the Titanium SDK core). Verify availability and compatibility before using in production.
86
+
105
87
  ```javascript
106
88
  // lib/services/encryption.js
107
89
  // AES-256 encryption for sensitive local data
90
+ // Requires: ti.crypto community module
108
91
 
109
92
  const crypto = require('ti.crypto')
110
93
 
@@ -125,30 +108,6 @@ exports.decrypt = function(encryptedData, key) {
125
108
  options: { mode: crypto.CBC }
126
109
  })
127
110
  }
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
111
  ```
153
112
 
154
113
  ## Secure HTTP communication
@@ -270,15 +229,15 @@ exports.Validator = {
270
229
 
271
230
  ## Owasp mobile security checklist
272
231
 
273
- | Category | Check | Implementation |
232
+ | Category | Check | Implementation |
274
233
  | -------------------- | --------------------------- | ---------------------------------- |
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 |
234
+ | **Data Storage** | Credentials stored securely | Keychain/KeyStore for tokens |
235
+ | **Data Storage** | Sensitive data encrypted | AES-256 for cached data |
236
+ | **Communication** | HTTPS only | `validatesSecureCertificate: true` |
237
+ | **Communication** | Certificate pinning | SSL pinning enabled |
238
+ | **Authentication** | Token refresh | Auto-refresh before expiry |
239
+ | **Authentication** | Session timeout | Auto-logout after inactivity |
240
+ | **Input Validation** | Server-side validation | Never trust client input |
241
+ | **Input Validation** | Sanitize user input | Remove XSS patterns |
242
+ | **Cryptography** | No hardcoded keys | Keys from secure storage |
243
+ | **Cryptography** | Use standard algorithms | AES-256, SHA-256 |
@@ -20,6 +20,7 @@ Create a single source of truth using Backbone.Events:
20
20
  ```javascript
21
21
  // lib/services/stateStore.js
22
22
  const Backbone = require('alloy/backbone')
23
+ const _ = require('alloy/underscore')._
23
24
 
24
25
  class StateStore {
25
26
  constructor() {
@@ -280,13 +281,13 @@ Ti.App.addEventListener('resume', () => {
280
281
 
281
282
  ## Anti-patterns
282
283
 
283
- | Anti-Pattern | Why It's Bad | Solution |
284
+ | Anti-Pattern | Why It's Bad | Solution |
284
285
  | ------------------------------------- | ---------------------------- | ---------------------------------------- |
285
- | `Ti.App.fireEvent` for state | No cleanup, memory leaks | Use StateStore with `offChange` |
286
- | Direct collection mutation | Bypasses reactivity | Use collection methods (`add`, `remove`) |
287
- | State in multiple places | Inconsistency bugs | Single source of truth |
288
- | Global variables (`Alloy.Globals`) | No reactivity, hard to track | Use StateStore |
289
- | Controller-to-controller direct calls | Tight coupling | Use StateStore or events |
286
+ | `Ti.App.fireEvent` for state | No cleanup, memory leaks | Use StateStore with `offChange` |
287
+ | Direct collection mutation | Bypasses reactivity | Use collection methods (`add`, `remove`) |
288
+ | State in multiple places | Inconsistency bugs | Single source of truth |
289
+ | Global variables (`Alloy.Globals`) | No reactivity, hard to track | Use StateStore |
290
+ | Controller-to-controller direct calls | Tight coupling | Use StateStore or events |
290
291
 
291
292
  ## Persistence strategies
292
293
 
@@ -472,14 +473,14 @@ exports.appStore = {
472
473
 
473
474
  ### Choosing a strategy
474
475
 
475
- | Data Type | Strategy | Reason |
476
- | ------------------ | ------------------- | ---------------------- |
477
- | User preferences | Ti.App.Properties | Simple main-value, fast |
478
- | Auth tokens | Keychain/KeyStore | Security |
479
- | User profile | Properties + Secure | Mixed sensitivity |
480
- | Lists (100+ items) | SQLite | Query, pagination |
481
- | Offline queue | SQLite | Durability, FIFO |
482
- | Cache | In-memory + SQLite | Speed + persistence |
476
+ | Data Type | Strategy | Reason |
477
+ | ------------------ | ------------------- | ----------------------- |
478
+ | User preferences | Ti.App.Properties | Simple main-value, fast |
479
+ | Auth tokens | Keychain/KeyStore | Security |
480
+ | User profile | Properties + Secure | Mixed sensitivity |
481
+ | Lists (100+ items) | SQLite | Query, pagination |
482
+ | Offline queue | SQLite | Durability, FIFO |
483
+ | Cache | In-memory + SQLite | Speed + persistence |
483
484
 
484
485
  ## State middleware
485
486