npm package: @s-spektra-25/zoho-asap-widget
Description: Standalone Angular library for Zoho Desk ASAP widget with built-in MSAL token exchange and session management
Author: CloudLabs
Latest version: 1.0.0

# Zoho Desk ASAP Widget - Standalone Angular Library

A **completely standalone Angular library** (10+) that wraps the [Zoho Desk ASAP 2.0 widget](https://desk.zoho.com/). This library handles **everything** internally: token exchange, authentication, session management, and storage clearing. Your app only needs to pass an MSAL token - no Zoho-specific code required.

## 🎯 Key Features

- ✅ **Truly Standalone**: Accepts MSAL token, handles JWT exchange internally
- ✅ **Zero Zoho Dependencies in App**: No AsapService, no JWT management, no storage clearing needed
- ✅ **Multi-User Safe**: Automatically detects user changes and clears sessions
- ✅ **Smart Token Refresh**: Distinguishes token refresh (same user) from user switch (different user)
- ✅ **Automatic Session Management**: Clears localStorage, sessionStorage, and cookies on logout
- ✅ **Prefill Support**: Automatic form prefilling with dynamic values
- ✅ **Angular 10-18+ Compatible**: Works with all modern Angular versions
- ✅ **Production Ready**: Clean code, no console logs, robust error handling

## 📦 Installation

```bash
npm install @cloudlabs/zoho-asap-widget
```

Import the module in your app:

```typescript
import { ZohoAsapWidgetModule } from '@cloudlabs/zoho-asap-widget';

@NgModule({
  imports: [ZohoAsapWidgetModule],
  // ...
})
export class AppModule { }
```

## 🚀 Basic Usage

**Prerequisites:** Your app must have MSAL (Microsoft Authentication Library) configured to authenticate users with Azure B2C or Azure AD. The widget requires an MSAL ID token.

```html
<!-- Simply pass your MSAL token to the widget -->
<zoho-asap-widget
  *ngIf="msalToken"
  [appId]="'YOUR_ZOHO_APP_ID'"
  [orgId]="'YOUR_ZOHO_ORG_ID'"
  [msalToken]="msalToken"
  [enabled]="true">
</zoho-asap-widget>
```

**That's it!** The widget handles:
- Token exchange (MSAL → Zoho JWT)
- User authentication with Zoho
- Session management
- Storage cleanup on logout
- User switching detection

### With Form Prefill

```html
<zoho-asap-widget
  *ngIf="msalToken"
  [appId]="'YOUR_ZOHO_APP_ID'"
  [orgId]="'YOUR_ZOHO_ORG_ID'"
  [msalToken]="msalToken"
  [enabled]="true"
  [prefillFields]="prefillData"
  [departmentId]="'YOUR_DEPT_ID'"
  [layoutId]="'YOUR_LAYOUT_ID'">
</zoho-asap-widget>
```

Where `prefillData` is an object in your component:
```typescript
prefillData: any = {
  subject: 'Support Request',              // Standard Zoho field
  cf_event_request_id: 'MS012345678910',  // Custom field
};
```

**Dynamic prefill example:**
```typescript
// Set subject dynamically with user name
this.prefillData.subject = `Support Request from ${this.userDisplayName}`;

// Update prefill based on user context
if (isPremiumUser) {
  this.prefillData = {
    subject: `Priority Support - ${userName}`,
    cf_priority: 'High',
    cf_user_type: 'Premium'
  };
}
```

## 🔧 Component Inputs

```typescript
interface ZohoAsapWidgetInputs {
  // Required
  appId: string;              // Zoho ASAP application ID
  orgId: string;              // Zoho organization ID
  msalToken: string;          // MSAL ID token (Azure B2C/AD)
  
  // Optional
  enabled?: boolean;          // Enable/disable widget (default: true)
  nonce?: string;             // Security nonce for script loading
  
  // Prefill (optional)
  prefillFields?: {           // Object with field names and values
    [fieldName: string]: any; // e.g., { cf_event_request_id: '12345', cf_priority: 'High' }
  };
  departmentId?: string;      // Department ID (required for prefill)
  layoutId?: string;          // Form layout ID (required for prefill)
}
```

## 🔐 How It Works

### Architecture

```
┌─────────────────┐
│   Your App      │
│  (MSAL only)    │
└────────┬────────┘
         │ MSAL Token
         ▼
┌─────────────────┐
│  Zoho Widget    │ ──► Backend API ──► Exchange Token ──► Zoho JWT
│   (Library)     │ ◄── Zoho JWT ◄────────────────────────
└────────┬────────┘
         │
         ▼ Login with JWT
┌─────────────────┐
│  Zoho Desk      │
│   ASAP API      │
└─────────────────┘
```

### Flow

1. **App authenticates user** via MSAL (Azure B2C/AD)
2. **App passes MSAL token** to widget via `[msalToken]` input
3. **Widget handles everything:**
   - Exchanges token for Zoho JWT internally
   - Logs into Zoho Desk ASAP
   - Manages session lifecycle
4. **On logout:** Widget clears all Zoho storage/cookies automatically

## 🧠 Smart User Detection

The widget intelligently handles two scenarios:

### Scenario 1: Same User, Token Refresh
```typescript
// User's MSAL token expires and gets refreshed
// Old: eyJhbGc...user@example.com...abc
// New: eyJhbGc...user@example.com...xyz

// Widget: Extracts user identity (email/oid)
// → Same user detected
// → Updates Zoho JWT without logout
// → User keeps their session state
```

### Scenario 2: Different User Login
```typescript
// User A logs out, User B logs in
// Old: eyJhbGc...userA@example.com...
// New: eyJhbGc...userB@example.com...

// Widget: Extracts user identity
// → Different user detected
// → Fully logs out User A
// → Clears all storage/cookies
// → Logs in User B with clean session
```

This prevents unnecessary session disruption while ensuring complete isolation between users.
```

## 🧹 Session Management (Automatic)

The widget handles **everything automatically** when component lifecycle changes:

```typescript
// When widget is destroyed (*ngIf becomes false):
// - Detects ngOnDestroy
// - Calls Zoho logout
// - Clears ALL Zoho storage (localStorage, sessionStorage, cookies)
// - Waits for logout confirmation event

// When widget is created fresh (*ngIf becomes true):
// - Clears any residual Zoho storage (safety check)
// - Loads Zoho script
// - Exchanges MSAL token for Zoho JWT
// - Logs into Zoho with fresh session
```

**Your app just controls the `*ngIf` condition - widget does the rest!**

## 🔄 Multi-User Scenarios

Perfect for applications where users switch accounts:

```typescript
// User A logged in
this.msalToken = 'eyJhbGc...userA@example.com...';
// Widget: Exchanges token, logs in User A

// User A logs out
this.msalToken = null;
// Widget: Detects token removal, logs out, clears all Zoho data

// User B logs in (component recreated)
this.msalToken = 'eyJhbGc...userB@example.com...';
// Widget: Fresh component → clears storage → new session
// User B sees ONLY their data, never User A's
```

## 📝 Form Prefilling

To prefill ticket fields when opening the widget, pass an object with field names and values:

```html
<zoho-asap-widget
  [appId]="'202588000344171472'"
  [orgId]="'649984843'"
  [msalToken]="msalToken"
  [prefillFields]="{
    subject: 'Technical Support Request',        // Standard field
    description: 'Issue details here',           // Standard field
    cf_event_request_id: 'EVENT-12345',         // Custom field
    cf_priority: 'High',                         // Custom field
    cf_category: 'Technical Issue',              // Custom field
    cf_user_email: userEmail,                    // Custom field with dynamic value
    cf_location: 'Building A'                    // Custom field
  }"
  [departmentId]="'202588000019741045'"
  [layoutId]="'202588000019743298'">
</zoho-asap-widget>
```

**Field Types:**

**Standard Zoho Fields** (no prefix needed):
- `subject` - Ticket subject/title
- `description` - Ticket description/body
- `email` - Contact email
- `phone` - Contact phone

**Custom Fields** (require `cf_` prefix):
- `cf_event_request_id` - Your custom event ID field
- `cf_priority` - Your custom priority field
- `cf_category` - Your custom category field
- `cf_*` - Any other custom field you've created

**Requirements:**
- `prefillFields`: Object where keys are field names (standard or custom)
- `departmentId`: Zoho department ID (required for prefill)
- `layoutId`: Zoho form layout ID (required for prefill)

**How to find field API names:**
1. Go to Zoho Desk > Setup > Channels > ASAP
2. Click on your department's form layout
3. Standard fields: Use lowercase names (`subject`, `description`, `email`)
4. Custom fields: Will have API names like `cf_event_request_id`, `cf_priority`

**Dynamic prefill with user context:**
```typescript
// Construct prefill data with user information
prefillData = {
  subject: `Support Request from ${userName}`,
  email: userEmail,
  cf_event_request_id: ticketId,
  cf_user_type: userType,
  cf_priority: priority
};

// Update specific fields dynamically
this.prefillData.cf_event_request_id = newTicketId;
this.prefillData.subject = `Updated Request - ${newTicketId}`;

// Widget automatically reloads when prefillFields object reference changes
this.prefillData = { ...this.prefillData };
```

The widget automatically:
- Configures prefill before loading the Zoho script
- Reapplies prefill after user login
- Reloads widget when `prefillFields` object changes

##  Important: What Your App Needs vs. Doesn't Need

### ✅ Your App Needs to Provide

1. **MSAL ID Token** - From Azure B2C/Azure AD authentication
   - Get this from your MSAL authentication service
   - Pass it to the widget: `[msalToken]="yourToken"`
   - Example: `@azure/msal-browser`, `@azure/msal-angular`, or any MSAL implementation

2. **Zoho Configuration** - Your Zoho Desk ASAP credentials
   - `appId`: From Zoho Desk ASAP setup
   - `orgId`: Your Zoho organization ID

3. **Optional: Prefill Data** - Custom field values for ticket forms
   - `prefillFields`: Object with field names and values
   - `departmentId` & `layoutId`: Required if using prefill

### ❌ Your App Does NOT Need

- ~~Any Zoho-specific services or logic~~
- ~~JWT token exchange code (widget calls backend internally)~~
- ~~Session management code~~
- ~~Storage/cookie clearing logic~~
- ~~Logout/login event handlers~~
- ~~User switching detection~~
- ~~Import anything except `ZohoAsapWidgetModule`~~

**Summary:** Just provide the MSAL token and Zoho config - the widget handles all token exchange and session management internally!

## 🏗️ Building & Publishing

```bash
# Build the library for production
ng build zoho-asap-widget --configuration production

# Navigate to dist folder
cd dist/zoho-asap-widget

# Publish to npm (one-time login: npm login)
npm publish --access public

# For updates, increment version first:
# npm version patch  # 1.0.0 -> 1.0.1
# npm version minor  # 1.0.0 -> 1.1.0
# npm version major  # 1.0.0 -> 2.0.0
```

## 🐛 Troubleshooting

### Widget shows old user's data after login
- **Cause:** Browser cache not cleared
- **Solution:** Widget now clears storage on `ngOnInit()` automatically
- **Manual fix:** Clear browser cache (Ctrl+Shift+Delete → All time)

### Backend API returns 405 Method Not Allowed
- **Cause:** Backend expects POST, library uses GET
- **Solution:** Change backend to accept GET requests

### Widget not loading
- **Check:** `appId` and `orgId` are correct
- **Check:** MSAL token is valid (not null/undefined)
- **Check:** Network connectivity to Zoho services

### Token refresh causes logout
- **Cause:** Should not happen - widget detects same user
- **Debug:** Check console for user identity extraction logs
- **Verify:** JWT payload has `oid`, `sub`, or `email` claim

## 🔗 Resources

- [Zoho Desk ASAP 2.0 Documentation](https://desk.zoho.com/portal/en/kb/articles/asap-widgets)

## 📄 License

MIT