Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ LinkForty.onDeepLink((url: string, data: DeepLinkData | null) => {

### `onDeferredDeepLink(callback)`

Listen for deferred deep links. Called on first launch after install if the user clicked a LinkForty link before installing.
Listen for deferred deep links. Called once, on the launch that reports the install: with the link's data if the user clicked a LinkForty link before installing, or `null` for an organic install. It can be registered before or after `init()`. Later launches don't call it; use `getInstallData()` for the install's link data.

```typescript
LinkForty.onDeferredDeepLink((data: DeepLinkData | null) => {
Expand Down
43 changes: 43 additions & 0 deletions __tests__/DeferredDeepLink.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { describe, it, expect, vi } from 'vitest';
import { DeferredDeepLink } from '../src/DeferredDeepLink';
import type { DeepLinkData } from '../src/types';

const data = { shortCode: 'abc123', linkId: 'link-1' } as DeepLinkData;

describe('DeferredDeepLink', () => {
it('calls a callback registered before the install is reported', () => {
const deferred = new DeferredDeepLink();
const callback = vi.fn();
deferred.register(callback);
deferred.deliver(data);

expect(callback).toHaveBeenCalledTimes(1);
expect(callback).toHaveBeenCalledWith(data);
});

it('calls a callback registered after the install is reported', () => {
const deferred = new DeferredDeepLink();
deferred.deliver(data);
const callback = vi.fn();
deferred.register(callback);

expect(callback).toHaveBeenCalledWith(data);
});

it('delivers null for an organic install registered after init', () => {
const deferred = new DeferredDeepLink();
deferred.deliver(null);
const callback = vi.fn();
deferred.register(callback);

expect(callback).toHaveBeenCalledWith(null);
});

it('calls nothing on a launch that does not report the install', () => {
const deferred = new DeferredDeepLink();
const callback = vi.fn();
deferred.register(callback);

expect(callback).not.toHaveBeenCalled();
});
});
27 changes: 27 additions & 0 deletions src/DeferredDeepLink.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { DeepLinkData, DeferredDeepLinkCallback } from './types';

/**
* Delivers the deferred deep link once, on the launch that reports the install:
* the link's data, or null for an organic install. A callback registered after
* that is called with the same result; on later launches nothing is delivered.
*
* Kept free of React Native imports so it can be tested (see urlParameters.ts).
*/
export class DeferredDeepLink {
private callback: DeferredDeepLinkCallback | null = null;
private delivered = false;
private data: DeepLinkData | null = null;

/** Registers the callback, replacing any earlier one. */
register(callback: DeferredDeepLinkCallback): void {
this.callback = callback;
if (this.delivered) callback(this.data);
}

/** Delivers the install's result. Called only on the launch that reports the install. */
deliver(data: DeepLinkData | null): void {
this.data = data;
this.delivered = true;
this.callback?.(data);
}
}
35 changes: 13 additions & 22 deletions src/LinkFortySDK.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { DeepLinkHandler } from './DeepLinkHandler';
import { AttributionContext } from './AttributionContext';
import { NavigationTracker } from './NavigationTracker';
import { LinkDomains } from './LinkDomains';
import { DeferredDeepLink } from './DeferredDeepLink';
import { SDK_NAME, SDK_VERSION } from './version';
import type {
LinkFortyConfig,
Expand All @@ -28,7 +29,8 @@ const STORAGE_KEYS = {
export class LinkFortySDK {
private config: LinkFortyConfig | null = null;
private deepLinkHandler: DeepLinkHandler | null = null;
private deferredDeepLinkCallback: DeferredDeepLinkCallback | null = null;
/** Delivers the deferred deep link on the launch that reports the install */
private deferredDeepLink = new DeferredDeepLink();
private installId: string | null = null;
private externalUserId: string | null = null;
private initialized: boolean = false;
Expand Down Expand Up @@ -165,17 +167,13 @@ export class LinkFortySDK {
}

/**
* Register callback for deferred deep links (new installs)
* Register callback for deferred deep links, called once on the launch that
* reports the install: with the link's data, or null for an organic install.
* Can be registered before or after init(). Later launches don't call it;
* use getInstallData() for the install's link data.
*/
onDeferredDeepLink(callback: DeferredDeepLinkCallback): void {
this.deferredDeepLinkCallback = callback;

// If we already have install data, call the callback immediately
this.getInstallData().then((data) => {
if (data && this.deferredDeepLinkCallback) {
this.deferredDeepLinkCallback(data);
}
});
this.deferredDeepLink.register(callback);
}

/**
Expand Down Expand Up @@ -533,10 +531,7 @@ export class LinkFortySDK {
await this.attribution.recordDeepLinkOpen(deepLinkData.linkId);
}

// Call deferred deep link callback if registered
if (this.deferredDeepLinkCallback) {
this.deferredDeepLinkCallback(deepLinkData);
}
this.deferredDeepLink.deliver(deepLinkData);

if (this.config.debug) {
console.log('[LinkForty] Install attributed with confidence:', response.confidenceScore);
Expand All @@ -547,18 +542,14 @@ export class LinkFortySDK {
console.log('[LinkForty] Install not attributed (organic install)');
}

// Call callback with null to indicate organic install
if (this.deferredDeepLinkCallback) {
this.deferredDeepLinkCallback(null);
}
// null indicates an organic install
this.deferredDeepLink.deliver(null);
}
} catch (error) {
console.error('[LinkForty] Failed to report install:', error);

// Call callback with null on error
if (this.deferredDeepLinkCallback) {
this.deferredDeepLinkCallback(null);
}
// null on error
this.deferredDeepLink.deliver(null);
}
}

Expand Down
Loading