Skip to content

Conversation

@vladislav-yermakov
Copy link
Collaborator

@vladislav-yermakov vladislav-yermakov commented Dec 18, 2025

This PR addresses the following issues:

Breaking changes:

  • OptableSDK.init(host:, app:, insecure:, useragent:) was replaced with OptableSDK.init(config:)
  • SDK now requires tenant and originSlug parameters to initialise, instead of host and app
  • OptableConfig was introduced to contain all configuration parameters for the SDK
  • OptableConfig supports dynamic changes of all parameters, such as host, path, apiKey, custopUserAgent and others.

Other changes:

  • Introduced IABConsent for automatically retrieving user consent according to the IAB Transparency & Consent Framework
  • Introduced AppTrackingTransparency to handle App Tracking Identifiers
  • Introduced OptableIdentifierType, OptableIdentifiers, and OptableIdentifierEncoder to support optable identifiers and normalisation
  • Introduced async/await SDK API for modern swift
  • Updated SDK Unit and Integration tests
  • Updated demo-ios-swift and demo-ios-objc to match new SDK APIs
  • Updated docs to reflect new APIs

dependencies: [],
path: "Source",
exclude: ["Source/Info.plist"]),
exclude: ["Info.plist"]),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we test SwiftPM integration on some minimal demo app?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This integration was manually tested using the demo-ios-swift project.

Do you want to create separate demo with the SPM integration?

@@ -0,0 +1,187 @@
## Usage (Objective-C)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if we continue to maintain objective-c API this doc needs to be updated too, unless it was already updated, but the APIs seem to be a bit different from those used in Swift version

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes APIs are different, for obj-c version the delegate approach was and still is used, i have not changed it.

For swift apis still use callbacks but also provides async/await syntax.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The obj-c doc was updated, and separated to this file.

}

/// Builds Enriched Identifier from Google GAID
static func gaid(_ gaid: String) -> String {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

funny to see on iOS, but let it be I guess

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code normalizes string according to Optable API Reference. To be consistent.

}

// MARK: - HTTPHeader
enum HTTPHeader: String {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this file looks like it has been copied from somewhere... is it genuinely coded by us, or was it taken from an Apache/MIT/any-other-permissive license source? can we give them credit?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file is written by us. Fully.

if ([email length] > 0) {
[output appendString:[NSString stringWithFormat:@"Email: %@\n", email]];
NSString *email = _identifyInput.text;
BOOL aaid = _identifyIDFA.isOn;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it actually read and sent ? can we also add output of the whole request URL, headers, and payload that we send with identify and targeting API calls, not just response that we display:
2026-01-08_14-50

2026-01-08_14-51

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No this toggle is not used, because on simulator the idfa comes as zero 000....-0000, and for the demo purposes IDFA was hardcoded.

I will remove the toggle and add the request info logs

Copy link
Collaborator Author

@vladislav-yermakov vladislav-yermakov Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updt:

As this is separate demo projects that use public-only SDK API, i can't provide request info on SDK consumer side, without breaking the API, because API only provides URLHTTPResponse object.

But there is work-around for this, i will rework logging.

appendOutput("ℹ️ Cache empty.\n")
}

loadBanner(keyvalues: tdata)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

targeting data is not just keyvalues. it is a rich object containing audience data and eids. specifically it is condensed under ortb2 key, f.e., here is the targeting data I see

[OptableSDK] Success on /targeting API call: {
    audience =     (
                {
            ids =             (
                                {
                    id = 082793f9;
                }
            );
            keyspace = "optable-test";
            provider = "optable.co";
            "rtb_segtax" = 5001;
        }
    );
    ortb2 =     {
        user =         {
            data =             (
                                {
                    id = "optable.co";
                    segment =                     (
                                                {
                            id = 082793f9;
                        }
                    );
                }
            );
            eids =             (
                                {
                    inserter = "optable.co";
                    matcher = "optable.co";
                    mm = 3;
                    source = "optable.co";
                    uids =                     (
                                                {
                            id = "c:custom.ABCDEFG";
                        },
                                                {
                            id = "c:new-custom.ABC";
                        },
                                                {
                            id = "e:04aff671ae26e58d662f7fa19e8400cd3943d20c1e7e4d05863e21be909fbc5b";
                        },
                                                {
                            id = "e:129c907cd4f2b4d95d76b63ec9661c64cddad5de53415a0116173bc324072968";
                        },
                                                {
                            id = "e:7e6ea62ad413e64265919670246b2992f4d15ca38b994c27298b63a588dd8ba8";
                        },
                                                {
                            id = "e:8dd86dbb33654227291e5abc33dafeb7803a4086c990f54c5a3749edbd198398";
                        },
                                                {
                            id = "e:911da1f8c9f6433b0fd4134a949c001b2d9a92228230c83de582a9fb01e31e0e";
                        },
                                                {
                            id = "e:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
                        },
                                                {
                            id = "v:0b5zRoKB03RnzAZJaneBN4";
                        },
                                                {
                            id = "v:2kozF4XZ7i87FSabsr3E9a";
                        },
                                                {
                            id = "v:3GyM7NmcaJ4cBj3eIZOrkh";
                        },
                                                {
                            id = "v:3VAn89d4ykHHQVQsJwyE7o";
                        },
                                                {
                            id = "v:6eLs6T7LvpKUu2SkMw0aST";
                        }
                    );
                },
                                {
                    inserter = "optable.co";
                    matcher = "optable.co";
                    mm = 3;
                    source = "uidapi.com";
                    uids =                     (
                                                {
                            atype = 3;
                            ext =                             {
                                optable =                                 {
                                    ref = 13;
                                };
                            };
                            id = "A4AAACTU1S_haNZJvbPH51bFaFA9dFgSZootbRgQNi_LVDTwF8F_ba9bpwzuChNYh_MqsLdLqqUrBN3TCsYkgewDVoUmmaWhXDxzZGi9ldOaYDPjygAi7oWwsNvKFY7ZZq-Wjh72_nab5s9NJzB8lKF45DM6B6KdN7uVghQ1-wfApZBb190ST9AbFD0yU2Ej64d2AM7zcvbd-vZ5NMBCF8IfkQ";
                        }
                    );
                },
                                {
                    inserter = "optable.co";
                    matcher = "optable.co";
                    mm = 3;
                    source = "criteo-hemapi.com";
                    uids =                     (
                                                {
                            atype = 3;
                            ext =                             {
                                stype = "cto_bundle_hem_api";
                            };
                            id = kEyE5l9BeGZxSzI0WG9FVjE4UnZFaVhmZUJ3V2lmV3lqNzBic2ttREwzcTBxaUtEYVYyWjFTeTN1MjYxJTJCN21XVW9sYnhMcGxSa2Q4UXg0YU55dVloUDFwS3NCdHVOOGwlMkJENUNwV0tJS1BtMzFwM1ElM0Q;
                        }
                    );
                }
            );
        };
    };
    refs =     {
        13 =         {
            "advertising_token" = "A4AAACTU1S_haNZJvbPH51bFaFA9dFgSZootbRgQNi_LVDTwF8F_ba9bpwzuChNYh_MqsLdLqqUrBN3TCsYkgewDVoUmmaWhXDxzZGi9ldOaYDPjygAi7oWwsNvKFY7ZZq-Wjh72_nab5s9NJzB8lKF45DM6B6KdN7uVghQ1-wfApZBb190ST9AbFD0yU2Ej64d2AM7zcvbd-vZ5NMBCF8IfkQ";
            "identity_expires" = 1768144718027;
            "refresh_expires" = 1770477518027;
            "refresh_from" = 1767889118027;
            "refresh_response_key" = "Swl0DS42zZ7v2ROaY5OSSmdfkxsbb3wxGnmSTORfXSA=";
            "refresh_token" = "AAAAJNUk7ydR6rTX2eh9hrT6TFXcBHZTn+ySeXzlU8hTCN7zWVg+cTATJcCrBkUam3C+7ygmfC4sY/o5qQ9ePy8JQrQZnfSHcbN9k8RctSmQlXB30uM4DOWu0CfcrGv9evJWh7FzwWLeQAuq315VEuaCdEzJva3g5emfzzF68oT1RWzlH9dFfW7A1V+4hfnay3k/+wyTm7M2xTIw1OIbT5/E4ID61A5Pet4yeo4C8n0fOWwHlH/ekCjqKn4aVu4hDNHdB3XkxeR5Gp1/WY6RrEruebN56jFpjnMNxFnhvUdTl7Kwb7wC6N4wRaK+xErwkoerzu7Lu09xEIch12cXpnukeTFln/SCqgLhbsGl7sXdvHJc1KdLQaujrDuk1U6xmPAM";
        };
    };
    "resolved_ids" =     (
        "v:0b5zRoKB03RnzAZJaneBN4"
    );
    user =     (
    );
}

this entire ortb2 object it should be merged into the Prebid SDK to make it form a correct OpenRTB request before making fetchDemand call. please consult @OlenaPostindustria on how to pass this data to Prebid.

So keyvalues parameter should be renamed into targetingData rather. the ortb2 should be properly passed to Prebid SDK, also there are GAM targeting keywords in the targeting data, but these are to be set as key=audience.keyspace value= coma separated ids, in the above case the only targeting key-value pair we pass to gam would be: optable-test=082793f9, if there were more audience ids, they would be coma-separated

Copy link
Collaborator Author

@vladislav-yermakov vladislav-yermakov Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't touch the logic of this code, this additions are just part of project file structure updates.

Should I leave them as they were, or investigate and apply your suggestions?


### Call tryIdentifyFromURL SDK API

When iOS launches your app after a user taps a universal link, you receive an `NSUserActivity` object with an `activityType` value of `NSUserActivityTypeBrowsingWeb`. The activity object's `webpageURL` property contains the URL that the user is accessing. You can then pass it to the SDK's `tryIdentifyFromURL()` API which will automatically look for `oeid` in the query string of the URL and call `identify` with its value if found.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this API actually work? or did we remove it? also it is peculiar that oeid value mentioned here is already sha256-hashed - so if it is passed as an email identifier to identify API - we need to make sure it is not hashed again. We might need a special parameter whether email needs to be hashed or not on the OptableIdentifiers or other object, by default it should be true, but in this particular case it needs to use raw value not hashing it.

Copy link
Collaborator Author

@vladislav-yermakov vladislav-yermakov Jan 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are old apis that were already in sdk.
The only thing I did was restructure repetitive docs, separating this to file (so that obj-c/swift version not duplicate each other).

  • APIs stays the same, as stated in docs-ios | docs-web.
  • Also there are some unit tests for this apis that were already in sdk and are successfully passing. ( you can find them here - search for eidFromURL() )

/// Builds Enriched Identifier from Postal code
static func postalCode(_ postalCode: String) -> String {
let prefix = OptableIdentifierType.postalCode.rawValue
let identifier = postalCode.components(separatedBy: CharacterSet.whitespacesAndNewlines).joined().lowercased()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are we sure we need to remove whitespace and newlines and also lowercase postal code? British postal codes always contain a space, and upper case letters - are we sure this "normalization" is needed? is it specified in the Optable docs?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code normalizes string according to Optable API Reference.

Though, I will change whitespaces and new lines to trim only on both ends of the string.

/// Builds Enriched Identifier from Apple IDFA
static func idfa(_ idfa: String) -> String {
let prefix = OptableIdentifierType.appleIDFA.rawValue
let identifier = idfa.components(separatedBy: CharacterSet.whitespacesAndNewlines).joined().lowercased()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need to normalize idfa this way? I think it can be passed in a raw form. we don't know what Apple comes up with - using upper case or lower case letters - I don't think other parts of the system would do with this ID, so I would not apply this normalization here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code normalizes string according to Optable API Reference.

@justadreamer
Copy link

@vladislav-yermakov could you please check the comments above ^ and also write a short description for this PR, like a summary of changes to the APIs. roughly there are 4 categories of changes:

  • public API
  • implementation (should not have changed dramatically, majority of features were preserved)
  • demo apps
  • tests

@eugenedorfman-optable eugenedorfman-optable marked this pull request as ready for review January 8, 2026 18:56
@justadreamer
Copy link

Here is how audience object is converted into GAM key-value pairs:

https://github.com/Optable/optable-web-sdk/blob/b1beb3e995e8bd5edad6987fe5c048fe359d80b3/lib/edge/targeting.ts#L148-L165

function TargetingKeyValues(tdata: TargetingResponse | null): TargetingKeyValues {
  const result: TargetingKeyValues = {};

  if (!tdata) {
    return result;
  }

  for (const identifiers of tdata.audience ?? []) {
    if (identifiers.keyspace) {
      if (!(identifiers.keyspace in result)) {
        result[identifiers.keyspace] = [];
      }
      result[identifiers.keyspace].push(...identifiers.ids.map((el) => el.id));
    }
  }

  return result;
}

audience is an array of objects:

    "audience": [
        {
            "provider": "optable.co",
            "ids": [
                {
                    "id": "15f30409"
                }
            ],
            "keyspace": "optable",
            "rtb_segtax": 5001
        }
    ],

each of these objects becomes a single key-value pair.

@justadreamer
Copy link

Re: setting targetingData.ortb2 to Prebid - seems it is done correctly in Android: https://github.com/Optable/optable-android-sdk/pull/35/files#diff-c55c975d4a7bd768528a4687e2fbc25857690b9457d09282cb847d1532d06867R152

@vladislav-yermakov
Copy link
Collaborator Author

@justadreamer Please review recent changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants