@livx.cc/appwrap 0.28.0 → 0.28.2

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 (40) hide show
  1. package/package.json +1 -1
  2. package/runtime/app/app.ts +55 -54
  3. package/runtime/app/shell/android-helpers.ts +6 -6
  4. package/runtime/app/shell/background-worker.android.ts +47 -0
  5. package/runtime/app/shell/background-worker.ios.ts +5 -0
  6. package/runtime/app/shell/background-worker.ts +7 -0
  7. package/runtime/app/shell/banner.ts +9 -4
  8. package/runtime/app/shell/bridge.ts +2 -0
  9. package/runtime/app/shell/custom-webview.android.ts +50 -23
  10. package/runtime/app/shell/custom-webview.ios.ts +90 -78
  11. package/runtime/app/shell/devmenu.ts +2 -2
  12. package/runtime/app/shell/env.ts +1 -3
  13. package/runtime/app/shell/events.ts +5 -4
  14. package/runtime/app/shell/handlers-android.ts +16 -12
  15. package/runtime/app/shell/handlers-background.ts +57 -78
  16. package/runtime/app/shell/handlers-billing.ts +90 -82
  17. package/runtime/app/shell/handlers-extended.ts +141 -110
  18. package/runtime/app/shell/handlers-fs.ts +53 -41
  19. package/runtime/app/shell/handlers-health.ts +27 -19
  20. package/runtime/app/shell/handlers-media.ts +8 -14
  21. package/runtime/app/shell/handlers-oauth.ts +19 -19
  22. package/runtime/app/shell/handlers-parity.ts +118 -89
  23. package/runtime/app/shell/handlers-push.ts +5 -3
  24. package/runtime/app/shell/handlers-reviews.ts +5 -6
  25. package/runtime/app/shell/handlers-scanner.ts +36 -38
  26. package/runtime/app/shell/handlers-speech.ts +43 -49
  27. package/runtime/app/shell/handlers-system.ts +1 -1
  28. package/runtime/app/shell/handlers.ts +4 -3
  29. package/runtime/app/shell/ios-frameworks.d.ts +9 -0
  30. package/runtime/app/shell/ios-ui-delegate.ts +112 -108
  31. package/runtime/app/shell/native-log.ts +1 -3
  32. package/runtime/app/shell/status-bar.ts +7 -4
  33. package/runtime/app/shell/toast.ts +1 -1
  34. package/runtime/references.d.ts +6 -0
  35. package/runtime/tsconfig.json +1 -0
  36. package/src/cli.ts +43 -18
  37. package/src/derive.ts +8 -0
  38. package/templates/ci/github/workflows/appwrap-pr.yml +3 -3
  39. package/templates/ci/github/workflows/appwrap-release-android.yml +2 -2
  40. package/templates/ci/github/workflows/appwrap-release-ios.yml +2 -2
@@ -2,8 +2,6 @@ import { Utils, isAndroid, isIOS } from '@nativescript/core';
2
2
  import { bridge } from './bridge';
3
3
  import { startActivityForResult } from './android-helpers';
4
4
 
5
- declare const android: any, java: any;
6
-
7
5
  const err = (code: string, message: string) => Object.assign(new Error(message), { code });
8
6
 
9
7
  type Dir = 'documents' | 'data' | 'cache';
@@ -152,6 +150,46 @@ function iosStat(full: string, name: string): any {
152
150
  };
153
151
  }
154
152
 
153
+ /**
154
+ * Delegate for UIDocumentPickerViewController. Captures the resolve callback as an instance field
155
+ * (the original closed over `resolve`); both selectors funnel into it.
156
+ */
157
+ @NativeClass()
158
+ class DocumentPickerDelegate extends NSObject implements UIDocumentPickerDelegate {
159
+ static ObjCProtocols = [UIDocumentPickerDelegate];
160
+ onPick!: (out: any[]) => void;
161
+
162
+ static new(): DocumentPickerDelegate {
163
+ return <DocumentPickerDelegate>super.new();
164
+ }
165
+
166
+ documentPickerDidPickDocumentsAtURLs(_controller: UIDocumentPickerViewController, urls: NSArray<NSURL>): void {
167
+ const out: any[] = [];
168
+ for (let i = 0; i < urls.count; i++) {
169
+ const url = urls.objectAtIndex(i);
170
+ const scoped = url.startAccessingSecurityScopedResource?.();
171
+ try {
172
+ const data = NSData.dataWithContentsOfURL(url);
173
+ if (data) {
174
+ out.push({
175
+ name: url.lastPathComponent,
176
+ mimeType: mimeForUrlIos(url),
177
+ size: data.length,
178
+ base64: data.base64EncodedStringWithOptions(0 as unknown as NSDataBase64EncodingOptions),
179
+ });
180
+ }
181
+ } finally {
182
+ if (scoped) url.stopAccessingSecurityScopedResource?.();
183
+ }
184
+ }
185
+ this.onPick(out);
186
+ }
187
+
188
+ documentPickerWasCancelled(_controller: UIDocumentPickerViewController): void {
189
+ this.onPick([]);
190
+ }
191
+ }
192
+
155
193
  /**
156
194
  * iOS document picker via UIDocumentPickerViewController. Reads each chosen URL's bytes (under a
157
195
  * security-scoped access window) and returns them base64-inline. DEVICE-GATED — the delegate
@@ -163,6 +201,7 @@ function pickFileIos(types: string[] | undefined, multiple: boolean): Promise<an
163
201
  try {
164
202
  const utis = NSMutableArray.new();
165
203
  // Map common MIME/extension hints to UTTypes; default to "any item".
204
+ // interop: UTType/UTTypeItem live in UniformTypeIdentifiers, not referenced by references.d.ts.
166
205
  (types ?? []).forEach((t) => {
167
206
  const ut = (UTType as any).typeWithMIMEType?.(t) ?? (UTType as any).typeWithFilenameExtension?.(t.replace(/^\./, ''));
168
207
  if (ut) utis.addObject(ut);
@@ -172,37 +211,9 @@ function pickFileIos(types: string[] | undefined, multiple: boolean): Promise<an
172
211
  const picker = UIDocumentPickerViewController.alloc().initForOpeningContentTypes(utis as any);
173
212
  picker.allowsMultipleSelection = multiple;
174
213
 
175
- const DelegateClass = (NSObject as any).extend(
176
- {
177
- documentPickerDidPickDocumentsAtURLs(_p: any, urls: any) {
178
- const out: any[] = [];
179
- for (let i = 0; i < urls.count; i++) {
180
- const url = urls.objectAtIndex(i);
181
- const scoped = url.startAccessingSecurityScopedResource?.();
182
- try {
183
- const data = NSData.dataWithContentsOfURL(url);
184
- if (data) {
185
- out.push({
186
- name: url.lastPathComponent,
187
- mimeType: mimeForUrlIos(url),
188
- size: data.length,
189
- base64: data.base64EncodedStringWithOptions(0 as unknown as NSDataBase64EncodingOptions),
190
- });
191
- }
192
- } finally {
193
- if (scoped) url.stopAccessingSecurityScopedResource?.();
194
- }
195
- }
196
- resolve(out);
197
- },
198
- documentPickerWasCancelled() {
199
- resolve([]);
200
- },
201
- },
202
- { protocols: [UIDocumentPickerDelegate] }
203
- );
204
- const delegate = DelegateClass.new();
205
- (picker as any)._appwrapDelegate = delegate; // retain past the present call
214
+ const delegate = DocumentPickerDelegate.new();
215
+ delegate.onPick = resolve;
216
+ (picker as any)._appwrapDelegate = delegate; // interop: retain delegate past the present call (no typed slot)
206
217
  picker.delegate = delegate;
207
218
  Utils.ios.getRootViewController().presentViewControllerAnimatedCompletion(picker, true, null);
208
219
  } catch (e: any) {
@@ -212,25 +223,26 @@ function pickFileIos(types: string[] | undefined, multiple: boolean): Promise<an
212
223
  });
213
224
  }
214
225
 
215
- function mimeForUrlIos(url: any): string {
226
+ function mimeForUrlIos(url: NSURL): string {
227
+ // interop: UTType lives in UniformTypeIdentifiers, not referenced by references.d.ts.
216
228
  const ut = (UTType as any).typeWithFilenameExtension?.(url.pathExtension);
217
229
  return ut?.preferredMIMEType ?? 'application/octet-stream';
218
230
  }
219
231
 
220
232
  // ───────────────────────────── Android ─────────────────────────────
221
233
 
222
- function androidBaseDir(dir: Dir): any {
234
+ function androidBaseDir(dir: Dir): java.io.File {
223
235
  const ctx = Utils.android.getApplicationContext();
224
236
  if (dir === 'cache') return ctx.getCacheDir();
225
237
  if (dir === 'documents') return ctx.getExternalFilesDir(null) ?? ctx.getFilesDir();
226
238
  return ctx.getFilesDir(); // 'data'
227
239
  }
228
240
 
229
- function androidFile(path: string, dir: Dir): any {
241
+ function androidFile(path: string, dir: Dir): java.io.File {
230
242
  return new java.io.File(androidBaseDir(dir), safeRelPath(path));
231
243
  }
232
244
 
233
- function readBytesAndroid(file: any): any {
245
+ function readBytesAndroid(file: java.io.File): androidNative.Array<number> {
234
246
  const fis = new java.io.FileInputStream(file);
235
247
  const baos = new java.io.ByteArrayOutputStream();
236
248
  const buf = Array.create('byte', 8192);
@@ -298,7 +310,7 @@ function registerAndroidFs(): void {
298
310
  bridge.register('fs.pickFile', ({ types, multiple }: any) => pickFileAndroid(types, !!multiple));
299
311
  }
300
312
 
301
- function writeAndroid(file: any, data: string, encoding: string, append: boolean): void {
313
+ function writeAndroid(file: java.io.File, data: string, encoding: string, append: boolean): void {
302
314
  const bytes = encoding === 'base64'
303
315
  ? android.util.Base64.decode(String(data ?? ''), android.util.Base64.DEFAULT)
304
316
  : new java.lang.String(String(data ?? '')).getBytes('UTF-8');
@@ -307,7 +319,7 @@ function writeAndroid(file: any, data: string, encoding: string, append: boolean
307
319
  fos.close();
308
320
  }
309
321
 
310
- function androidStat(file: any, name: string): any {
322
+ function androidStat(file: java.io.File, name: string): any {
311
323
  const isDir = file.isDirectory();
312
324
  return {
313
325
  name,
@@ -338,7 +350,7 @@ async function pickFileAndroid(types: string[] | undefined, multiple: boolean):
338
350
  const { resultCode, intent: result } = await startActivityForResult(intent);
339
351
  if (resultCode !== android.app.Activity.RESULT_OK || !result) return [];
340
352
 
341
- const uris: any[] = [];
353
+ const uris: android.net.Uri[] = [];
342
354
  const clip = result.getClipData?.();
343
355
  if (clip) for (let i = 0; i < clip.getItemCount(); i++) uris.push(clip.getItemAt(i).getUri());
344
356
  else if (result.getData()) uris.push(result.getData());
@@ -361,7 +373,7 @@ async function pickFileAndroid(types: string[] | undefined, multiple: boolean):
361
373
  });
362
374
  }
363
375
 
364
- function queryDisplayName(cr: any, uri: any): string {
376
+ function queryDisplayName(cr: android.content.ContentResolver, uri: android.net.Uri): string {
365
377
  const cursor = cr.query(uri, [android.provider.OpenableColumns.DISPLAY_NAME], null, null, null);
366
378
  let name = 'file';
367
379
  if (cursor) {
@@ -2,9 +2,7 @@ import { Utils, isAndroid, isIOS } from '@nativescript/core';
2
2
  import { bridge } from './bridge';
3
3
  import { requestPermissions, startActivityForResult } from './android-helpers';
4
4
 
5
- declare const android: any;
6
- declare const cc: any; // generated from the HealthConnectBridge.kt shim (overrides/)
7
- declare const CMPedometer: any; // CoreMotion (already linked via CMMotionManager in handlers-parity)
5
+ declare const cc: any; // no NS types: generated Kotlin HealthConnectBridge (HealthConnectBridge.kt shim, overrides/)
8
6
 
9
7
  const err = (code: string, message: string) => Object.assign(new Error(message), { code });
10
8
 
@@ -39,7 +37,8 @@ function registerIos(): void {
39
37
  new Promise((resolve) => {
40
38
  if (!HKHealthStore.isHealthDataAvailable()) { resolve(false); return; }
41
39
  const readSet = NSSet.setWithObject(stepType);
42
- store.requestAuthorizationToShareTypesReadTypesCompletion(null, readSet, (ok: boolean, e: any) => {
40
+ // null typesToShare (read-only request) — SDK types the param as a non-nullable NSSet.
41
+ store.requestAuthorizationToShareTypesReadTypesCompletion(null as any, readSet, (ok: boolean, e: NSError) => {
43
42
  Utils.dispatchToMainThread(() => resolve(!!ok && !e));
44
43
  });
45
44
  })
@@ -54,9 +53,12 @@ function registerIos(): void {
54
53
  // WKWebView response delivery (evaluateJavaScript) is main-thread.
55
54
  bridge.register('health.count', () =>
56
55
  new Promise((resolve, reject) => {
57
- const predicate = HKQuery.predicateForSamplesWithStartDateEndDateOptions(startOfToday(), NSDate.date(), 0);
56
+ // NS marshals NSDate→the SDK's `Date` param type; options 0 = HKQueryOptions.None.
57
+ const predicate = HKQuery.predicateForSamplesWithStartDateEndDateOptions(
58
+ startOfToday() as unknown as Date, NSDate.date() as unknown as Date, HKQueryOptions.None
59
+ );
58
60
  const q = HKStatisticsQuery.alloc().initWithQuantityTypeQuantitySamplePredicateOptionsCompletionHandler(
59
- stepType, predicate, HKStatisticsOptions.CumulativeSum, (_q: any, stats: any, e: any) => {
61
+ stepType, predicate, HKStatisticsOptions.CumulativeSum, (_q: HKStatisticsQuery, stats: HKStatistics, e: NSError) => {
60
62
  Utils.dispatchToMainThread(() => {
61
63
  if (e) { reject(err('NATIVE_ERROR', e.localizedDescription ?? 'health query failed')); return; }
62
64
  const sum = stats && stats.sumQuantity ? stats.sumQuantity() : null;
@@ -72,17 +74,19 @@ function registerIos(): void {
72
74
  // aggregate. Caller uses HealthKit as the periodic source-of-truth and these live deltas in between.
73
75
  // Emits `health.liveStep` { steps } (today's pedometer total since local midnight). iPhone-only
74
76
  // (no Watch), which is fine: it's the live driver; HealthKit re-anchors the authoritative total.
75
- let pedometer: any = null;
77
+ let pedometer: CMPedometer | null = null;
76
78
  let anchorDay = -1; // local day-of-month the current stream is anchored to (for midnight re-anchor)
77
79
  const localDay = () => new Date().getDate();
78
80
  const startStream = () => {
81
+ if (!pedometer) return;
79
82
  anchorDay = localDay();
80
- pedometer.startPedometerUpdatesFromDateWithHandler(startOfToday(), (data: any, e: any) => {
83
+ // startOfToday() returns an NSDate; the SDK types `start` as JS Date but NativeScript marshals it.
84
+ pedometer.startPedometerUpdatesFromDateWithHandler(startOfToday() as unknown as Date, (data: CMPedometerData, e: NSError) => {
81
85
  if (e || !data) return;
82
86
  // Midnight self-heal: CMPedometer counts since the FIXED anchor date, so after local midnight
83
87
  // numberOfSteps still includes yesterday. When the day rolls over, restart from the new
84
88
  // midnight (re-anchors to 0) so the live count stays "today only".
85
- if (localDay() !== anchorDay) { Utils.dispatchToMainThread(() => { pedometer.stopPedometerUpdates(); startStream(); bridge.emit('health.liveStep', { steps: 0 }); }); return; }
89
+ if (localDay() !== anchorDay) { Utils.dispatchToMainThread(() => { pedometer?.stopPedometerUpdates(); startStream(); bridge.emit('health.liveStep', { steps: 0 }); }); return; }
86
90
  const steps = Math.round(num(data.numberOfSteps));
87
91
  Utils.dispatchToMainThread(() => bridge.emit('health.liveStep', { steps }));
88
92
  });
@@ -91,9 +95,12 @@ function registerIos(): void {
91
95
  if (typeof CMPedometer === 'undefined' || !CMPedometer.isStepCountingAvailable())
92
96
  throw err('UNSUPPORTED', 'Pedometer (live step counting) unavailable on this device');
93
97
  // Motion & Fitness denied/restricted → surface it so the caller can fall back to HealthKit instead
94
- // of silently receiving no events. (2 = denied, 1 = restricted per CMAuthorizationStatus.)
95
- const auth = typeof CMPedometer.authorizationStatus === 'function' ? CMPedometer.authorizationStatus() : 0;
96
- if (auth === 2 || auth === 1) throw err('DENIED', 'Motion & Fitness access is off for live step counting');
98
+ // of silently receiving no events.
99
+ const auth = typeof CMPedometer.authorizationStatus === 'function'
100
+ ? CMPedometer.authorizationStatus()
101
+ : CMAuthorizationStatus.NotDetermined;
102
+ if (auth === CMAuthorizationStatus.Denied || auth === CMAuthorizationStatus.Restricted)
103
+ throw err('DENIED', 'Motion & Fitness access is off for live step counting');
97
104
  if (pedometer) return; // already streaming
98
105
  pedometer = CMPedometer.new();
99
106
  startStream();
@@ -117,29 +124,30 @@ function registerAndroid(): void {
117
124
  };
118
125
 
119
126
  // ── sensor fallback (TYPE_STEP_COUNTER) — when Health Connect isn't available ──
120
- let sensorManager: any = null;
121
- let listener: any = null;
127
+ let sensorManager: android.hardware.SensorManager | null = null;
128
+ let listener: android.hardware.SensorEventListener | null = null;
122
129
  let baseline: number | null = null;
123
130
  let latest: number | null = null;
124
131
  let registered = false;
125
132
  const ensureSensor = () => {
126
- if (!sensorManager) sensorManager = ctx().getSystemService(android.content.Context.SENSOR_SERVICE);
133
+ // getSystemService is typed `any` by the SDK; narrow to the concrete manager.
134
+ if (!sensorManager) sensorManager = ctx().getSystemService(android.content.Context.SENSOR_SERVICE) as android.hardware.SensorManager;
127
135
  };
128
136
  const stopSensor = () => {
129
- if (registered) { sensorManager.unregisterListener(listener); registered = false; }
137
+ if (registered && sensorManager && listener) { sensorManager.unregisterListener(listener); registered = false; }
130
138
  };
131
139
  const startSensor = () => {
132
140
  ensureSensor();
133
- const sensor = sensorManager.getDefaultSensor(android.hardware.Sensor.TYPE_STEP_COUNTER);
141
+ const sensor = sensorManager!.getDefaultSensor(android.hardware.Sensor.TYPE_STEP_COUNTER);
134
142
  if (!sensor) throw err('UNSUPPORTED', 'No step-counter sensor on this device');
135
143
  stopSensor();
136
144
  baseline = null;
137
145
  latest = null;
138
146
  listener = new android.hardware.SensorEventListener({
139
- onSensorChanged: (ev: any) => { const v = num(ev.values[0]); latest = v; if (baseline == null) baseline = v; },
147
+ onSensorChanged: (ev: android.hardware.SensorEvent) => { const v = num(ev.values[0]); latest = v; if (baseline == null) baseline = v; },
140
148
  onAccuracyChanged: () => {},
141
149
  });
142
- sensorManager.registerListener(listener, sensor, android.hardware.SensorManager.SENSOR_DELAY_NORMAL);
150
+ sensorManager!.registerListener(listener, sensor, android.hardware.SensorManager.SENSOR_DELAY_NORMAL);
143
151
  registered = true;
144
152
  };
145
153
  const sensorCount = () => ({ steps: baseline != null && latest != null ? Math.max(0, Math.round(latest - baseline)) : 0 });
@@ -1,30 +1,22 @@
1
1
  import { Utils, isIOS } from '@nativescript/core';
2
2
  import { bridge } from './bridge';
3
3
 
4
- // AVAudioSession globals (NS resolves these from the AVFoundation metadata at runtime).
5
- declare const AVAudioSession: any;
6
- declare const AVAudioSessionCategoryPlayAndRecord: string;
7
- declare const AVAudioSessionPortBuiltInReceiver: string;
8
- declare const AVAudioSessionRouteChangeNotification: string;
9
- declare const AVAudioSessionPortOverride: any;
10
- declare const NSNotificationCenter: any;
11
-
12
4
  let routeObserverArmed = false;
13
5
 
14
6
  /** List the current output port types (e.g. "Receiver", "Speaker", "BluetoothA2DP"). */
15
- function outputPortTypes(session: any): string[] {
7
+ function outputPortTypes(session: AVAudioSession): string[] {
16
8
  const outputs = session.currentRoute?.outputs;
17
9
  const types: string[] = [];
18
10
  for (let i = 0; i < (outputs?.count ?? 0); i++) types.push(String(outputs.objectAtIndex(i).portType));
19
11
  return types;
20
12
  }
21
13
 
22
- function isOnBuiltInReceiver(session: any): boolean {
14
+ function isOnBuiltInReceiver(session: AVAudioSession): boolean {
23
15
  return outputPortTypes(session).indexOf(AVAudioSessionPortBuiltInReceiver) !== -1;
24
16
  }
25
17
 
26
18
  /** Whether the session can record (PlayAndRecord) — i.e. we're (or about to be) in a call. */
27
- function isRecordCategory(session: any): boolean {
19
+ function isRecordCategory(session: AVAudioSession): boolean {
28
20
  return String(session.category) === String(AVAudioSessionCategoryPlayAndRecord);
29
21
  }
30
22
 
@@ -33,7 +25,7 @@ function forceSpeakerIfReceiverDuringCall(): void {
33
25
  const session = AVAudioSession.sharedInstance();
34
26
  if (!isRecordCategory(session) || !isOnBuiltInReceiver(session)) return; // not a call, or already speaker/headset/BT
35
27
  try {
36
- session.overrideOutputAudioPortError(AVAudioSessionPortOverride.Speaker, null);
28
+ session.overrideOutputAudioPortError(AVAudioSessionPortOverride.Speaker);
37
29
  } catch {
38
30
  /* benign — route may have changed again under us */
39
31
  }
@@ -60,6 +52,7 @@ function observeAudioRouteForSpeaker(): void {
60
52
  */
61
53
  export function registerMediaHandlers(): void {
62
54
  // DIAGNOSTIC: surface the WKWebView UIDelegate status to the page (on-screen log).
55
+ // no NS types: __appwrapWebviewDiag is an ad-hoc diagnostic stashed on global by the WKWebView UIDelegate.
63
56
  bridge.register('debug.webviewInfo', () => ({ diag: (global as any).__appwrapWebviewDiag ?? 'n/a (android/web)' }));
64
57
 
65
58
  // Auto loudspeaker for in-WebView WebRTC calls. When getUserMedia is live, WebKit drives the
@@ -120,7 +113,8 @@ export function registerMediaHandlers(): void {
120
113
  } else {
121
114
  category = AVAudioSessionCategoryPlayback; // ignores the silent switch
122
115
  }
123
- session.setCategoryModeOptionsError(category, avMode, options as any, null as any);
124
- session.setActiveError(true, null as any);
116
+ // options is an OR-combined flag mask; the bitwise result widens to number, so cast back to the enum.
117
+ session.setCategoryModeOptionsError(category, avMode, options as AVAudioSessionCategoryOptions);
118
+ session.setActiveError(true);
125
119
  });
126
120
  }
@@ -2,17 +2,28 @@ import { Application, Utils, isAndroid, isIOS } from '@nativescript/core';
2
2
  import { bridge } from './bridge';
3
3
  import { setDeepLinkInterceptor } from './events';
4
4
 
5
- // AuthenticationServices globals (NS auto-links the framework via metadata when referenced).
6
- declare const ASWebAuthenticationSession: any;
7
- declare const ASWebAuthenticationPresentationContextProviding: any;
8
- declare const android: any, androidx: any;
5
+ declare const androidx: any; // no NS types: androidx
9
6
 
10
7
  const err = (code: string, message: string) => Object.assign(new Error(message), { code });
11
8
 
12
9
  // Keep strong refs while the sheet is up — ARC frees the session + its context provider the moment
13
10
  // the JS locals fall out of scope, which silently cancels the flow mid-handshake.
14
- let activeSession: any = null;
15
- let activeProvider: any = null;
11
+ let activeSession: ASWebAuthenticationSession | null = null;
12
+ let activeProvider: ASWebAuthenticationPresentationContextProviding | null = null;
13
+
14
+ @NativeClass()
15
+ class OAuthPresentationContextProvider extends NSObject implements ASWebAuthenticationPresentationContextProviding {
16
+ static ObjCProtocols = [ASWebAuthenticationPresentationContextProviding];
17
+ static new(): OAuthPresentationContextProvider {
18
+ return <OAuthPresentationContextProvider>super.new();
19
+ }
20
+ presentationAnchorForWebAuthenticationSession(_session: ASWebAuthenticationSession): UIWindow {
21
+ return (
22
+ Utils.ios.getRootViewController()?.view?.window ??
23
+ UIApplication.sharedApplication.keyWindow
24
+ );
25
+ }
26
+ }
16
27
 
17
28
  /**
18
29
  * System-browser OAuth (iOS ASWebAuthenticationSession). Runs the provider's auth page in the
@@ -37,7 +48,7 @@ export function registerOAuthHandlers(): void {
37
48
  const session = ASWebAuthenticationSession.alloc().initWithURLCallbackURLSchemeCompletionHandler(
38
49
  NSURL.URLWithString(target),
39
50
  scheme,
40
- (callbackURL: any, error: any) => {
51
+ (callbackURL: NSURL, error: NSError) => {
41
52
  activeSession = null;
42
53
  activeProvider = null;
43
54
  if (error) {
@@ -50,18 +61,7 @@ export function registerOAuthHandlers(): void {
50
61
  }
51
62
  );
52
63
 
53
- const ProviderClass = (NSObject as any).extend(
54
- {
55
- presentationAnchorForWebAuthenticationSession(_session: any): any {
56
- return (
57
- Utils.ios.getRootViewController()?.view?.window ??
58
- UIApplication.sharedApplication.keyWindow
59
- );
60
- },
61
- },
62
- { protocols: [ASWebAuthenticationPresentationContextProviding] }
63
- );
64
- activeProvider = ProviderClass.new();
64
+ activeProvider = OAuthPresentationContextProvider.new();
65
65
  session.presentationContextProvider = activeProvider;
66
66
  session.prefersEphemeralWebBrowserSession = !!ephemeral;
67
67
  activeSession = session;