# Audience Segment External Feed Methods

## Generic

Those are the mandatory methods to implement.

### instanceContextBuilder

This method

* validates feed properties and selected [identifying resources](https://developer.mediarithmics.io/user-points/user-identifiers).
* simplifies the instance handling by managing in one place properties, credentials, configurations, api clients...

It is called once at the beginning of the feed instantiation.\
It may be called again in the 3 other generic methods: [`onExternalSegmentCreation`](#onexternalsegmentcreation), [`onExternalSegmentConnection`](#onexternalsegmentconnection) and [`onUserSegmentUpdate`](#onusersegmentupdate) if we want to refresh the instance context.

We have to define a custom interface for this extended context, so we can return whatever we want in it.

```typescript
instanceContextBuilder(feedId: string): Promise<InstanceContext>
```

#### Input parameters

There is only one input parameter `feedId`.

<details>

<summary><code>feedId</code> (string, required):</summary>

</details>

#### Return

This method returns a context of type `AudienceFeedConnectorBaseInstanceContext` or an extended context of type `InstanceContext`.\
We recommend you to **use the extended context** in order to simplify the reuse of objects and variables in the plugin code.

The original context format contains the feed metadata and its properties.

<details>

<summary><code>ctx</code> (AudienceFeedConnectorBaseInstanceContext)</summary>

The original format of the AudienceFeedConnectorBaseInstanceContext type is

| Parameter        | Type                                | Description                                                                              |
| ---------------- | ----------------------------------- | ---------------------------------------------------------------------------------------- |
| `feed`           | AudienceSegmentExternalFeedResource | Represents the external feed resource for the audience segment.                          |
| `feedProperties` | PropertiesWrapper                   | Wrapper for properties associated with the feed, containing metadata and configurations. |

With the object `feed` (AudienceSegmentExternalFeedResource) that represents the external feed resource for the audience segment:

<table data-full-width="false"><thead><tr><th width="240.03125">Parameter</th><th width="219.2265625">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>id</code></td><td>string</td><td>Unique identifier for the audience segment external feed.</td></tr><tr><td><code>plugin_id</code></td><td>string</td><td>Identifier for the plugin associated with the feed.</td></tr><tr><td><code>organisation_id</code></td><td>string</td><td>Identifier for the organization that owns the feed.</td></tr><tr><td><code>group_id</code></td><td>string</td><td>Identifier for the group to which the feed belongs.</td></tr><tr><td><code>artifact_id</code></td><td>string</td><td>Identifier for the specific plugin artifact related to the feed.</td></tr><tr><td><code>version_id</code></td><td>string</td><td>Version identifier for the feed, indicating its versioning.</td></tr><tr><td><code>selected_identifying_resources</code></td><td>IdentifyingResourceShape<br><em>optional</em></td><td>Optional array of identifying resources used for segment identification.</td></tr><tr><td><code>created_by</code></td><td>string</td><td>Optional identifier for the user that created the feed.</td></tr></tbody></table>

</details>

The extended context contains the feed metadata, the feed properties, plus a bunch of useful variables and objects.

<details>

<summary><code>instanceContext</code> (InstanceContext)</summary>

```typescript
export interface InstanceContext extends AudienceFeedConnectorBaseInstanceContext
```

Those are the variables and objects that extend the original `context` (AudienceFeedConnectorBaseInstanceContext`)`.

<table><thead><tr><th width="156.3203125">Parameter</th><th width="228.0625">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>technicalConfig</code></td><td>TechnicalConfiguration</td><td>Configuration details for technical aspects.</td></tr><tr><td><code>loggerPrefix</code></td><td>string</td><td>A prefix for logging to identify the source of log messages.</td></tr><tr><td><code>segment</code></td><td>AudienceSegmentResource</td><td>The audience segment resource being processed.</td></tr><tr><td><code>properties</code></td><td>Properties</td><td>Plugin properties of the created feed.</td></tr><tr><td><code>credentials</code></td><td>Credentials</td><td>Credentials for external systems.</td></tr><tr><td><code>datamartConfig</code></td><td>DatamartConfiguration</td><td>Configuration specific to the datamart.</td></tr><tr><td><code>micsApiClient</code></td><td>MicsApiClient</td><td>Client for interacting with the MICS API.</td></tr><tr><td><code>partnerClient</code></td><td>PartnerClient</td><td>Client for interacting with the partner solution.</td></tr></tbody></table>

</details>

#### Code

There is only one input parameter `feedId`, but you must retrieve the context `ctx` at the beginning of the method code to extend it, like this:

```typescript
const ctx = await super.instanceContextBuilder(feedId);
```

<details>

<summary>Code example</summary>

```typescript
export interface InstanceContext extends AudienceFeedConnectorBaseInstanceContext {
  technicalConfig: TechnicalConfiguration;
  loggerPrefix: string;
  segment: AudienceSegmentResource;
  properties: Properties;
  credentials: Credentials;
  datamartConfig: DatamartConfiguration;
  datamartCountryMapping?: DatamartCountryMapping;
  micsApiClient: MicsApiClient;
  linkedinClient: LinkedInClient;
}
```

```typescript
protected async instanceContextBuilder(feedId: string): Promise<InstanceContext> {
  const ctx = await super.instanceContextBuilder(feedId);
  const segment: AudienceSegmentResource = await this.fetchAudienceSegment(feedId);
  const loggerPrefix = `Segment: ${segment.id} - Feed: ${feedId} - Datamart: ${segment.datamart_id}`;

  try {
    const { credentials, technicalConfig, datamartConfig, datamartCountryMapping } =
      await this.retrieveConfigurationsFiles({
        ctx,
        segment,
        loggerPrefix,
      });

    const partnerAccountId = this.findStringProperty<string>({
      findStringProperty: ctx.feedProperties.findStringProperty,
      name: PluginProperties.PARTNER_ACCCOUNT_ID,
      required: this.IS_MANDATORY,
    });

    if (!partnerAccountId) {
      throw new Error(`${PluginProperties.PARTNER_ACCCOUNT_ID} is mandatory, but not found`);
    }

    const segmentName =
      this.findStringProperty<string | undefined>({
        findStringProperty: ctx.feedProperties.findStringProperty,
        name: PluginProperties.SEGMENT_NAME,
        required: this.NOT_MANDATORY,
      }) || segment.name;

    const partnerSegmentId = this.findStringProperty<string | undefined>({
      findStringProperty: ctx.feedProperties.findStringProperty,
      name: PluginProperties.PARTNER_SEGMENT_ID,
      required: this.NOT_MANDATORY,
    });

    const properties = {
      partnerAccountId,
      segmentName,
      partnerSegmentId,
    };

    const selectedIdentifyingResources = ctx.feed.selected_identifying_resources;

    if (!selectedIdentifyingResources || !selectedIdentifyingResources.length) {
      throw new Error('No Identifying Resource found');
    }

    const micsApiClient = MicsApiClient.getInstance({
      apiToken: credentials.mics_api_token,
      loggerPrefix,
      logger: this.logger,
    });

    const partnerClient = new PartnerClient({
      credentials,
      partnerApiVersion: technicalConfig.partner_api_version,
      partnerEndpoint: technicalConfig.partner_endpoint,
      loggerPrefix,
      logger: this.logger,
    });

    this.logger.debug(`${loggerPrefix} - Instance Context set up for segment ${segment.id}.`);

    const customCtx = {
      ...ctx,
      technicalConfig,
      loggerPrefix,
      segment,
      properties,
      credentials,
      datamartConfig,
      datamartCountryMapping,
      micsApiClient,
      linkedinClient,
    };

    return customCtx;
  } catch (err) {
    this.logger.error(`${loggerPrefix} - instanceContextBuilder -- an error occured: ${(err as Error).message}`);
    throw err;
  }
}
```

</details>

### onExternalSegmentCreation

This method checks wether the audience already exist on the partner platform, or if it must create it. In this last case, we create the audience.

Also, through the [instance context method](#instancecontextbuilder), this `onExternalSegmentCreation` method validates again the feed properties and selected [identifying resources](https://developer.mediarithmics.io/user-points/user-identifiers).

{% hint style="info" %}
This method is called synchronously.
{% endhint %}

```typescript
onExternalSegmentCreation(request: ExternalSegmentCreationRequest, ctx: InstanceContext): promise<ExternalSegmentCreationPluginResponse>
```

#### Input parameters

<details>

<summary><code>request</code> (ExternalSegmentCreationRequest, required): An object containing the details necessary for creating the segment. It is sent by the platform at the feed creation.</summary>

`request` object format:

<table><thead><tr><th width="128.609375">Parameter</th><th width="80.76171875">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>feed_id</code></td><td>string</td><td>The unique identifier of the feed</td></tr><tr><td><code>segment_id</code></td><td>string</td><td>The unique identifier of the segment related to the feed</td></tr><tr><td><code>datamart_id</code></td><td>string</td><td>The identifier for the datamart of the segment</td></tr></tbody></table>

</details>

<details>

<summary><code>context</code> (InstanceContext, required): The context in which the feed creation is executed, containing various configuration and resource objects.</summary>

Check the [instance context section](#instancecontextbuilder) to understand the format of the feed instance context.\
You can find the extended context interface in the returns tab.

</details>

#### Return

Resolves to an object containing the status of the segment creation process, along with an optional message and visibility.

<details>

<summary><code>Promise&#x3C;ExternalSegmentCreationPluginResponse></code></summary>

`ExternalSegmentCreationPluginResponse` object format:

| Parameter    | Type                                                            |
| ------------ | --------------------------------------------------------------- |
| `status`     | enum\['ok' \| 'error' \| 'retry' \| 'no\_eligible\_identifier'] |
| `message`    | string                                                          |
| `visibility` | enum\['PRIVATE' \| 'PUBLIC']                                    |

</details>

#### Code

<details>

<summary>Code example</summary>

```typescript
// Trigger by the creation of a feed instance
protected async onExternalSegmentCreation(
    _: ExternalSegmentCreationRequest,
    ctx: InstanceContext,
  ): Promise<ExternalSegmentCreationPluginResponse> {
    this.logger.debug(`${ctx.loggerPrefix} - onExternalSegmentCreation step`);

    try {
      if ( !hasIdentifyingResource(ctx) ) {
        throw new Error(
          `Your User Profiles do not contain enough information for Linkedin connector to rely on them alone. Either an ID (mobile id or hashed email) OR the couple first and last name are required. Please contact your account manager.`,
        );
      }

      if (ctx.feedProperties.partnerSegmentId) {
        return await knownPartnerSegmentIdWorkflow({
          partnerSegmentId: ctx.feedProperties.partnerSegmentId,
          ctx,
          logger: this.logger,
        });
      }

      return unknownPartnerSegmentIdWorkflow({ ctx, logger: this.logger });
    } catch (err) {
      this.logger.error(`${ctx.loggerPrefix} - onExternalSegmentCreation - something went wrong:`, err);
      return {
        status: 'error',
        message: (err as Error).message,
        visibility: 'PUBLIC',
      };
    }
  }
```

</details>

### onExternalSegmentConnection

Establishing connection with segment on destination platform.

This method check the availability of the segment on the destination platform.

Also, through the [instance context method](#instancecontextbuilder), this `onExternalSegmentConnection` method may validate feed properties and selected [identifying resources](https://developer.mediarithmics.io/user-points/user-identifiers). It is not mandatory at this step as it was supposed to be done beforehand.

```typescript
onExternalSegmentConnection(request: ExternalSegmentConnectionRequest, context: InstanceContext): promise<ExternalSegmentCreationPluginResponse>
```

#### Input parameters

<details>

<summary><code>request</code> (ExternalSegmentConnectionRequest, required): An object containing the details necessary for creating the segment. It is sent by the platform at the feed creation.</summary>

`request` object format:

<table><thead><tr><th width="145.93359375">Variable</th><th width="108.78125">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>feed_id</code></td><td>string</td><td>The unique identifier of the feed</td></tr><tr><td><code>segment_id</code></td><td>string</td><td>The unique identifier of the segment related to the feed</td></tr><tr><td><code>datamart_id</code></td><td>string</td><td>The identifier for the datamart of the segment</td></tr></tbody></table>

</details>

<details>

<summary><code>context</code> (InstanceContext, required): The context in which the feed creation is executed, containing various configuration and resource objects.</summary>

Check the [instance context section](#instancecontextbuilder) to understand the format of the feed instance context.\
You can find the extended context interface in the returns tab.

</details>

#### Return

Resolves to an object containing the status of the segment creation process, along with an optional message and visibility.

<details>

<summary><code>Promise&#x3C;ExternalSegmentCreationPluginResponse></code></summary>

`ExternalSegmentCreationPluginResponse` object format:

<table><thead><tr><th width="223.015625">Variable</th><th>Type</th></tr></thead><tbody><tr><td><code>status</code></td><td>enum['ok' | 'error' | 'retry' | 'no_eligible_identifier']</td></tr><tr><td><code>message</code></td><td>string</td></tr><tr><td><code>visibility</code></td><td>enum['PRIVATE' | 'PUBLIC']<br><em>optional</em></td></tr></tbody></table>

</details>

#### Code

<details>

<summary>Code example</summary>

```typescript
protected async onExternalSegmentConnection(
    _: ExternalSegmentConnectionRequest,
    ctx: InstanceContext,
  ): Promise<ExternalSegmentConnectionPluginResponse> {
    this.logger.debug(`${ctx.loggerPrefix} - onExternalSegmentConnection step.`);

    try {
      if (!ctx.properties.partnerSegmentId) {
        throw new Error(`partnerSegmentId property is undefined while it should be by now`);
      }

      const partnerSegmentInfos = await ctx.partnerApiClient.fetchDMPSegment(ctx.properties.partnerSegmentId);

      this.logger.debug(
        `${ctx.loggerPrefix} - onExternalSegmentCreation - The partner segment Id (${ctx.properties.partnerSegmentId}) was retrieved: `,
        linkedinSegmentInfos,
      );

      return Promise.resolve({ status: 'ok' });
    } catch (err) {
      if ((err as CustomError).statusCode === 404) {
        return {
          status: 'external_segment_not_ready_yet',
          message: 'partner segment is not ready yet',
        };
      }
      this.logger.error(`${ctx.loggerPrefix} - onExternalSegmentConnection: something went wrong: `, err);
      return {
        status: 'error',
        message: (err as Error).message,
      };
    }
}
```

</details>

### onUserSegmentUpdate

This method sends the user identifiers (insertion or deletion) or prepares the payload to be sent to the partner platform.

{% hint style="info" %}
Depending on the plugin output type, this method will have different roles and return different objects.
{% endhint %}

* **Single API calls (classic plugin)**

  This method sends the user identifiers (insertion or deletion) to the partner platform.
* **Batch API calls**

  This method inserts the user identifiers in the batch through the returned object - it can be for both insertion or deletion.

  The batch will be sent to the partner platform through the [onBatchUpdate](#onbatchupdate) method.
* **File delivery**

  This method inserts the user identifiers in the file through the returned object - it can be for both insertion or deletion.

Also, through the [instance context method](#instancecontextbuilder), this `onUserSegmentUpdate` method may again validate feed properties and selected [identifying resources](https://developer.mediarithmics.io/user-points/user-identifiers). It is not mandatory at this step again as it was supposed to be done beforehand.

```typescript
onUserSegmentUpdate(request: UserSegmentUpdateRequest, context: InstanceContext): promise<BatchedUserSegmentUpdatePluginResponse<Payload>>
```

#### Input parameters

<details>

<summary><code>request</code> (UserSegmentUpdateRequest, required): An object containing the details necessary for creating the segment. It is sent by the platform at the feed creation.</summary>

`request` object format:

<table><thead><tr><th width="165.0390625">Parameter</th><th width="163.85546875">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>feed_id</code></td><td>string</td><td>unique identifier for the feed associated with the user segment update</td></tr><tr><td><code>session_id</code></td><td>string</td><td>identifier for the session during which the update is being made</td></tr><tr><td><code>datamart_id</code></td><td>string</td><td>identifier for the datamart where the user segment resides</td></tr><tr><td><code>segment_id</code></td><td>string</td><td>unique identifier for the segment being updated</td></tr><tr><td><code>user_identifiers</code></td><td>UserIdentifierInfo[]</td><td>array of user identifiers from the segment to send to the partner platform</td></tr><tr><td><code>user_profiles</code></td><td>UserProfileInfo[]</td><td>array of user profiles from the segment to send to the partner platform</td></tr><tr><td><code>ts</code></td><td>number</td><td>timestamp indicating when the update request was created</td></tr><tr><td><code>operation</code></td><td>UpdateType</td><td>type of update operation being performed, indicating whether it is an add, remove, or modify operation</td></tr></tbody></table>

</details>

<details>

<summary><code>context</code> (InstanceContext, required): The context in which the feed creation is executed, containing various configuration and resource objects.</summary>

Check the [instance context section](#instancecontextbuilder) to understand the format of the feed instance context.\
You can find the extended context interface in the returns tab.

</details>

#### Return (batch case)

Resolves to an object containing the batch of users identifiers to update on the segment destination platform.

<details>

<summary><code>Promise&#x3C;BatchedUserSegmentUpdatePluginResponse&#x3C;Payload>></code></summary>

`BatchedUserSegmentUpdatePluginResponse<Payload>` object format:

<table><thead><tr><th width="198.32421875">Parameter</th><th width="334.72265625">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>status</code></td><td>enum['ok' | 'error' | 'retry' | 'no_eligible_identifier']</td><td></td></tr><tr><td><code>data</code></td><td>UserSegmentUpdatePluginBatchDeliveryResponseData&#x3C;Payload>[], optional</td><td></td></tr><tr><td><code>stats</code></td><td>UserSegmentUpdatePluginResponseStats[], optional</td><td></td></tr><tr><td><code>message</code></td><td>string, optional</td><td></td></tr><tr><td><code>next_msg_delay_in_ms</code></td><td>number, optional</td><td></td></tr></tbody></table>

</details>

#### Code

<details>

<summary>Code example (batch case)</summary>

```typescript
protected async onUserSegmentUpdate(
    request: UserSegmentUpdateRequest,
    ctx: InstanceContext,
  ): Promise<BatchedUserSegmentUpdatePluginResponse<Payload>> {
    this.logger.debug(`${ctx.loggerPrefix} - onUserSegmentUpdate step.`);

    try {
      const payloads = await buildPayload({ ctx, request, logger: this.logger });

      if (!payloads) {
        if (this.logger.level === 'debug') {
          this.logger.warn(`No elements available for Batch Delivery for request ${JSON.stringify(request)}.`);
        }
        return Promise.resolve({ status: 'ok' });
      }

      return Promise.resolve({
        status: 'ok',
        data: payloads.map((payload) => {
          return {
            type: 'BATCH_DELIVERY',
            grouping_key: `${ctx.segment.id}_${ctx.feed.id}`,
            content: payload,
          };
        }),
      });
    } catch (err) {
      this.logger.error(`${ctx.loggerPrefix} - onUserSegmentUpdate: something went wrong: `, err);
      return {
        status: 'error',
        message: (err as Error).message,
      };
    }
  }
```

</details>

### onTroubleshoot

This method is utilized to troubleshoot the feed using audience data from the partner platform.

```typescript
onTroubleshoot(request: TroubleshootActionFetchDestinationAudience, ctx: InstanceContext): promise<ExternalSegmentTroubleshootResponse>
```

#### Input parameters

<details>

<summary><code>request</code> (TroubleshootActionFetchDestinationAudience, required): An object containing the details necessary for handling the troubleshoot.</summary>

| Parameter     | Type                                  | Description                                              |
| ------------- | ------------------------------------- | -------------------------------------------------------- |
| `feed_id`     | string                                | The unique identifier of the feed                        |
| `segment_id`  | string                                | The unique identifier of the segment related to the feed |
| `datamart_id` | string                                | The identifier for the datamart of the segment           |
| `action`      | enum\['FETCH\_DESTINATION\_AUDIENCE'] |                                                          |

</details>

<details>

<summary><code>context</code> (InstanceContext, required): The context in which the feed creation is executed, containing various configuration and resource objects.</summary>

Check the [instance context section](#instancecontextbuilder) to understand the format of the feed instance context.\
You can find the extended context interface in the returns tab.

</details>

#### Return

Resolves to an object containing the status of the segment creation process, along with an optional message and visibility.

<details>

<summary><code>Promise&#x3C;ExternalSegmentCreationPluginResponse></code></summary>

`ExternalSegmentCreationPluginResponse` object format:

<table><thead><tr><th width="117.16015625">Parameter</th><th width="222.3671875">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>status</code></td><td>enum['ok' | 'error' | 'retry' | 'no_eligible_identifier']</td><td></td></tr><tr><td><code>message</code></td><td>string</td><td></td></tr><tr><td><code>data</code></td><td>any, optional</td><td>Data to be displayed in the troubleshooting section of the feed card. It must provide as much data as possible to compare identifiers received, stored and used by the partner platform</td></tr></tbody></table>

</details>

#### Code

<details>

<summary>Code example</summary>

```typescript
protected async onTroubleshoot(
    request: TroubleshootActionFetchDestinationAudience,
    ctx: InstanceContext,
  ): Promise<ExternalSegmentTroubleshootResponse> {
    if (request.action === 'FETCH_DESTINATION_AUDIENCE') {
      if (!ctx.properties.partnerSegmentId) {
        throw new Error(`partnerSegmentId property is undefined while it should be by now`);
      }

      const partnerSegmentInfos = await ctx.partnerClient.fetchDMPSegment(ctx.properties.partnerSegmentId);

      const troubleshootData = {
        matchedRate: this.computeMatchRate(partnerSegmentInfos),
        audienceInfos: partnerSegmentInfos,
      };

      return {
        status: 'ok',
        message: 'Here is the destination audience!',
        data: troubleshootData,
      };
    }

    return { status: 'not_implemented' };
  }
```

</details>

## Batching

This method is required if we batch the calls to the partner API or to send a flat file.

### onBatchUpdate

Send the batched payload of user identifiers to the partner platform.

This payload is prepared by our platform thanks to the unitary calls to the [onUserSegmentUpdate](#onusersegmentupdate) method.

```typescript
onBatchUpdate(request: BatchUpdateRequest<AudienceFeedBatchContext, T>, ctx: instanceContext): Promise<BatchUpdatePluginResponse>
```

#### Input parameters

<details>

<summary><code>request</code> (BatchUpdateRequest&#x3C;AudienceFeedBatchContext, Payload>)</summary>

<table><thead><tr><th width="146.57421875">Parameter</th><th width="231.94921875">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>batch_content</code></td><td>T[]</td><td>Array of items (insertion/deletion) to be sent to the partner platform</td></tr><tr><td><code>ts</code></td><td>number</td><td>Timestamp indicating when the batch update request was created</td></tr><tr><td><code>context</code></td><td>AudienceFeedBatchContext</td><td>Context information relevant to the batch update process<br><em>We do not use this variable from this parameter.</em></td></tr></tbody></table>

</details>

<details>

<summary><code>context</code> (InstanceContext, required): The context in which the feed creation is executed, containing various configuration and resource objects.</summary>

Check the [instance context section](#instancecontextbuilder) to understand the format of the feed instance context.\
You can find the extended context interface in the returns tab.

</details>

#### Return

Resolves to an object containing the status of the users identifiers sent to the destination platform, along with an optional message.

<details>

<summary><code>Promise&#x3C;BatchUpdatePluginResponse></code></summary>

`BatchUpdatePluginResponse` object format:

<table><thead><tr><th width="284.83984375">Parameter</th><th>Type</th></tr></thead><tbody><tr><td><code>status</code></td><td>enum['OK' | 'ERROR' | 'RETRY']</td></tr><tr><td><code>message</code></td><td>string, optional</td></tr><tr><td><code>next_msg_delay_in_ms</code></td><td>number, optional</td></tr><tr><td><code>sent_items_in_success</code></td><td>number</td></tr><tr><td><code>sent_items_in_error</code></td><td>number</td></tr></tbody></table>

</details>

#### Code

<details>

<summary>Code example</summary>

```typescript
protected async onBatchUpdate(
  request: BatchUpdateRequest<AudienceFeedBatchContext, Payload>,
  ctx: InstanceContext,
): Promise<BatchUpdatePluginResponse> {
  this.logger.debug(`${ctx.loggerPrefix} - onBatchUpdate - starting.`);

  try {
    if (!ctx.properties.partnerSegmentId) {
      throw new Error(`partnerSegmentId property is undefined while it should be by now`);
    }

    if (!request.batch_content.length) {
      this.logger.debug(`${ctx.loggerPrefix} - onBatchUpdate - Empty batch content. Returning`);
      return {
        status: 'OK',
        sent_items_in_success: 0,
        sent_items_in_error: 0,
      };
    }

    // SendPayloads is a function that sends the batched users to the partner platform.
    const updateStats = await ctx.partnerClient.SendPayload({
      partnerSegmentId: ctx.properties.partnerSegmentId,
      payload: request.batch_content,
    });

    this.logger.debug(
      `${ctx.loggerPrefix} - onBatchUpdate - total sent users: ${updateStats.total} / success: ${updateStats.successful} / errors: ${updateStats.errors} / errors details: `,
      { errorDetails: updateStats.messages },
    );

    return Promise.resolve({
      status: 'OK',
      sent_items_in_success: updateStats.successful,
      sent_items_in_error: updateStats.errors,
    });
  } catch (err) {
    this.logger.error(`${ctx.loggerPrefix} - onBatchUpdate - something went wrong:`, err);
    return {
      status: 'ERROR',
      message: (err as Error).message,
      sent_items_in_success: 0,
      sent_items_in_error: request.batch_content.length,
    };
  }
}
```

</details>

## Authentication

### onAuthenticationStatusQuery

Check the authentication status of the connector. If the connector has no valid authentication, it must respond with a url to let the user authenticate through the desired method.

```typescript
onAuthenticationStatusQuery(request: ExternalSegmentAuthenticationStatusQueryRequest): promise<ExternalSegmentAuthenticationStatusQueryResponse>
```

#### Input parameters

<details>

<summary><code>request</code> (ExternalSegmentAuthenticationStatusQueryRequest): Request sent at authentication checking.</summary>

| Parameter           | Type                        | Description                                                                 |
| ------------------- | --------------------------- | --------------------------------------------------------------------------- |
| `segment_id`        | string, optional            | Optional identifier for the segment being queried                           |
| `datamart_id`       | string                      | Identifier for the datamart associated with the authentication status query |
| `plugin_version_id` | string                      | Identifier for the version of the plugin being used                         |
| `user_id`           | string                      | Unique identifier for the user making the authentication status query       |
| `properties`        | PluginProperty\[], optional | Optional array of properties related to the plugin for additional context   |

</details>

#### Return

Resolves to an object containing the status of the authentication, and the login url if not authenticated.

<details>

<summary><code>Promise&#x3C;ExternalSegmentAuthenticationStatusQueryResponse></code></summary>

`ExternalSegmentAuthenticationStatusQueryResponse` object format:

| Parameter | Type                                                                            | Description                                                                     |
| --------- | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| `status`  | enum\['authenticated' \| 'not\_authenticated' \| 'error' \| 'not\_implemented'] | Status of the authentication process indicating success or                      |
| `message` | string, optional                                                                | Message providing additional information about the authentication               |
| `data`    | object, optional                                                                | Optional object containing additional data related to the authentication status |

data object format:

| Parameter   | Type             | Description                                                    |
| ----------- | ---------------- | -------------------------------------------------------------- |
| `login_url` | string, optional | Optional URL for the login page if authentication is           |
| *`any key`* | string, optional | Additional properties can be included in this object as needed |

</details>

#### Code

<details>

<summary>Code example</summary>

```typescript
protected async onAuthenticationStatusQuery(
  request: ExternalSegmentAuthenticationStatusQueryRequest,
): Promise<ExternalSegmentAuthenticationStatusQueryResponse> {
  this.logger.info(`onAuthenticationStatusQuery - Checking for User: ${request.user_id}.`);

  try {
    const usersCredentials = await this.retrieveUsersCrendentials();
    const userCredential = usersCredentials[request.user_id];

    if (!userCredential) {
      this.logger.info(
        `onAuthenticationStatusQuery - No credentials found for user: ${request.user_id}. Starting authentication flow.`,
      );

      const encryption_key = await this.retrieveEncryptionKey()
      const client_id = await this.retrieveMicsClientId()

      const state = `datamart_id:${request.datamart_id}&user_id:${request.user_id}`;
      const encryptedState = this.encryptString(state, encryption_key);
      const encodedState = encodeURIComponent(encryptedState);

      const micsRedirectUri = `https://navigator.mediarithmics.com/v2/plugin_versions/${request.plugin_version_id}/auth_callback`;
      const loginUrl = `https://www.partner.com/oauth/authorization?response_type=code&client_id=${client_id}&redirect_uri=${micsRedirectUri}&state=${encodedState}`;

      return {
        status: 'not_authenticated',
        message: `No credentials found in the users credentials file for user: ${request.user_id}.`,
        data: {
          login_url: loginUrl,
        },
      };
    }

    const isRefreshTokenValid = await this.checkRefreshTokenValidity(request.user_id);

    if (!isRefreshTokenValid) {
      return {
        status: 'not_authenticated',
        message: `User ${request.user_id} was logged out due to invalid refresh token.`,
      };
    }

    return {
      status: 'authenticated',
      message: `User ${request.user_id} is authenticated.`,
    };
  } catch (err) {
    this.logger.error(`onAuthenticationStatusQuery - something went wrong:`, err);
    return {
      status: 'error',
      message: (err as Error).message,
    };
  }
}
```

</details>

### onAuthentication

Handle the redirection from the partner authentication flow and authenticate the connection within mediarithmics.

```typescript
onAuthentication(request: Request): promise<Response>
```

#### Input parameters

<details>

<summary><code>request</code> (ExternalSegmentAuthenticationRequest): Request forwarded from the partner response after authentication on their </summary>

<table><thead><tr><th width="177.91015625">Parameter</th><th width="144.09375">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>user_id</code></td><td>string</td><td>unique identifier for the user making the authentication request</td></tr><tr><td><code>plugin_version_id</code></td><td>string</td><td>identifier for the version of the plugin being used for authentication</td></tr><tr><td><code>params</code></td><td>object, optional<br><code>[key: string]</code></td><td>Optional object containing additional parameters received in the redirection from the partner authentication. Concerning the <em>Oauth2.0</em> protocol, the code is present within this array</td></tr></tbody></table>

</details>

#### Return

Resolves to an object containing the response status along the optional message.

<details>

<summary><code>Promise&#x3C;ExternalSegmentAuthenticationResponse></code></summary>

<table><thead><tr><th width="117.359375">Parameter</th><th width="225.2578125">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>status</code></td><td>enum['ok' | 'error' | 'not_implemented']</td><td>Status of the authentication process indicating success or failure</td></tr><tr><td><code>message</code></td><td>string, optional</td><td>optional message providing additional information about the authentication status</td></tr></tbody></table>

</details>

#### Code

<details>

<summary>Code example</summary>

```typescript
protected async onAuthentication(
  request: ExternalSegmentAuthenticationRequest,
): Promise<ExternalSegmentAuthenticationResponse> {
  this.logger.info(
    `onAuthentication - Response received from partner. Converting User ${request.user_id}'s code into a refresh token and storing it.`,
  );

  try {
    if (!request.params || !Object.keys(request.params).length) {
      throw new Error('No params found in request.');
    }

    const encryption_key = await this.retrieveEncryptionKey()
    const expectedKeys = ['code', 'state'];
    const missingKeys = expectedKeys.filter((key) => !request.params?.[key]);

    if (missingKeys.length) {
      throw new Error(`Missing keys in request.params: ${missingKeys.join(', ')}`);
    }

    const encryptedState = request.params['state'];
    const decryptedState = this.decryptString(encryptedState, encryption_key);

    const state = this.parseAndValidateState(decryptedState);

    this.logger.info(`onAuthentication - state decrypted and parsed:`, state);

    const code = request.params['code'];
      
    const refresh_token = await retrieveRefresehToken(code);
    await storeRefreshToken(refresh_token);

    this.logger.info(
      `onAuthentication - Refresh token received and stored for user ${request.user_id}.`,
    );

    return {
      status: 'ok',
    };
  } catch (err) {
    const requestError = (err as RequestError).response?.body || 'Unknown error';
    this.logger.error(`onAuthentication - something went wrong:`, {
      error: requestError
        ? typeof requestError === 'string'
          ? requestError
          : JSON.stringify(requestError)
        : JSON.stringify(err as Error),
    });
    return {
      status: 'error',
      message: (err as Error).message,
    };
  }
}
```

</details>

### onLogout

Handle the connector logout for either a user, or a datamart.

```typescript
onLogout(request: ExternalSegmentLogoutRequest): Promise<ExternalSegmentLogoutResponse>
```

#### Input parameters

<details>

<summary><code>request</code> (ExternalSegmentLogoutRequest): Request sent when logging out</summary>

<table><thead><tr><th width="179.76171875">Variable</th><th width="101.23828125">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>user_id</code></td><td>string</td><td>Unique identifier for the user making the logout request</td></tr><tr><td><code>plugin_version_id</code></td><td>string</td><td>Identifier for the plugin version being used for the logout </td></tr><tr><td><code>datamart_id</code></td><td>string</td><td>Identifier for the datamart associated with the logout request</td></tr></tbody></table>

</details>

#### Return

Resolves to an object containing the response status along the optional message.

<details>

<summary><code>Promise&#x3C;ExternalSegmentLogoutResponse></code></summary>

| Parameter | Type                                         | Description                                                                       |
| --------- | -------------------------------------------- | --------------------------------------------------------------------------------- |
| `status`  | enum\['ok' \| 'error' \| 'not\_implemented'] | Status of the authentication process indicating success or failure                |
| `message` | string, optional                             | Optional message providing additional information about the authentication status |

</details>

#### Code

<details>

<summary>Code example</summary>

```typescript
protected async onLogout(request: ExternalSegmentLogoutRequest): Promise<ExternalSegmentLogoutResponse> {
  this.logger.info(`onLogout - Loging out User: ${request.user_id}.`);

  try {
    const userLoggedOut = await this.logoutUser(request.user_id);
    return {
      status: 'ok',
      message: `Successfully logged out user ${request.user_id}.`,
    };
  } catch (err) {
    this.logger.error(`onLogout - something went wrong:`, err);
    return {
      status: 'error',
      message: (err as Error).message,
    };
  }
}
```

</details>

## Dynamic properties

### onDynamicPropertyValuesQuery

Enable the feed creation form to dynamically suggest values from the partner solution, allowing users to select from these options. Additionally, one dynamic property may depend on another.

```typescript
onDynamicPropertyValuesQuery(request: ExternalSegmentDynamicPropertyValuesQueryRequest): promise<Response>
```

#### Input parameters

<details>

<summary><code>request</code> (ExternalSegmentDynamicPropertyValuesQueryRequest): Request coming from the feed creation form.</summary>

<table><thead><tr><th width="144.828125">Parameter</th><th width="218.77734375">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>segment_id</code></td><td>string, optional</td><td>Optional identifier for the segment related to the feed being created</td></tr><tr><td><code>datamart_id</code></td><td>string</td><td>Identifier for the datamart associated with the feed being created</td></tr><tr><td><code>user_id</code></td><td>string</td><td>Unique identifier for the user making the query</td></tr><tr><td><code>properties</code></td><td>PluginProperty[], optional</td><td>Optional array of properties already set in the feed creation form</td></tr></tbody></table>

To better understand the `properties` array, here is an example type that can be found within it:

<table><thead><tr><th width="191.3828125">Parameter</th><th>Type</th></tr></thead><tbody><tr><td><code>property_type</code></td><td>enum['STRING', 'URL', ...]</td></tr><tr><td><code>value</code></td><td><p>object[]</p><ul><li><code>value</code> (string, optional)</li><li><code>id</code> (string, optional)</li><li>... depending on the property type</li></ul></td></tr></tbody></table>

</details>

#### Return

Resolves to an object containing the array of the dynamic properties and their values to be displayed in the feed creation form.

<details>

<summary><code>Promise&#x3C;ExternalSegmentDynamicPropertyValuesQueryResponse></code></summary>

<table><thead><tr><th width="118.74609375">Parameter</th><th width="177.14453125">Type</th><th>Description</th></tr></thead><tbody><tr><td><code>status</code></td><td>enum['ok' | 'error' | 'not_implemented']</td><td>status of the dynamic property values query indicating whether the operation was successful, encountered an error, or is not implemented</td></tr><tr><td><code>message</code></td><td>string, optional</td><td>Optional message providing additional information about the query status</td></tr><tr><td><code>data</code></td><td>object[], optional</td><td>Optional array of objects containing the dynamic property values</td></tr></tbody></table>

Object format for each dynamic property present in the `data` array:

| Parameter                 | Type      | Description                                                        |
| ------------------------- | --------- | ------------------------------------------------------------------ |
| `property_technical_name` | string    |                                                                    |
| `enum`                    | object\[] | Array of objects representing the possible values for the property |

Object format for each value present in the `enum` array:

| Parameter | Type   | Description                                                            |
| --------- | ------ | ---------------------------------------------------------------------- |
| `label`   | string | Human-readable label for the property value displayed in the dropdown  |
| `value`   | string | Actual value associated with the property (technical most of the time) |

</details>

#### Code

<details>

<summary>Code example</summary>

```typescript
protected async onDynamicPropertyValuesQuery(
  request: ExternalSegmentDynamicPropertyValuesQueryRequest,
): Promise<ExternalSegmentDynamicPropertyValuesQueryResponse> {
  this.logger.info(
    `onDynamicPropertyValuesQuery - User: ${request.user_id} (Datamart: ${request.datamart_id}) requested the dynamic properties: `,
    request.properties,
  );

  try {
    const partnerAccountIdStrProps = request.properties?.find(
      (property) =>
        property.technical_name === (PluginProperties.PARTNER_ACCCOUNT_ID as string) &&
        property.property_type === 'STRING',
    ) as StringProperty | undefined;

    const partnerAccountId = partnerAccountIdStrProps?.value?.value;

    if (!partnerAccountId) {
      const partnerAccountsList = await this.retrievePartnerAccountsList(request);

      return {
        status: 'ok',
        message: `Successfully retrieved ${partnerAccountsList.length} partner accounts for user ${request.user_id}.`,
        data: [
          {
            property_technical_name: PluginProperties.PARTNER_ACCCOUNT_ID,
            enum: partnerAccountsList,
          },
        ],
      };
    }

    const partnerAccountsList = await this.retrievePartnerAccountsList(request, partnerAccountId);
    const segmentsList = await this.retrieveExistingSegments(request, partnerAccountId);

    return {
      status: 'ok',
      message: `Successfully retrieved ${segmentsList.length} segments for partner account: ${partnerAccountId}.`,
      data: [
        {
          property_technical_name: PluginProperties.PARTNER_ACCCOUNT_ID,
          enum: partnerAccountsList,
        },
        {
          property_technical_name: PluginProperties.PARTNER_SEGMENT_ID,
          enum: segmentsList,
        },
      ],
    };
  } catch (err) {
    const errorBody = (err as RequestError).response?.body;
    this.logger.error(`onDynamicPropertyValuesQuery - something went wrong:`, { ...err, errorBody });
    return {
      status: 'error',
      message: (err as Error).message,
    };
  }
}
```

</details>
