SuperAPI AI Directory

add-office365-users

Office & productivity

View repository

Updated 10-16 06:47

About this skill

📋 Shared Instructions: shared-instructions.md — Cross-cutting concerns.

Add Office 365 Users (Wrapper)

This skill is a thin wrapper. Use /add-data-source as the single implementation path.

Delegation contract

Invoke /add-data-source with:

  • api-id: shared_office365users
  • mode: action

Office 365 Users: Method Selection Guide

After the connector is added, import the generated service:

import { Office365UsersService } from '../../generated/services/Office365UsersService'

Use the V2 methods for profiles and reporting relationships:

Scenario Method Result
Current signed-in user MyProfile_V2($select?) One GraphUser_V1
Specific user by ID or UPN UserProfile_V2(id, $select?) One GraphUser_V1
User's manager Manager_V2(id, $select?) One GraphUser_V1
Direct reports DirectReports_V2(id, $select?, $top?) Profiles in result.data.value
Search people SearchUserV2(searchTerm, top?, ...) Search response in result.data.value
Profile photo UserPhotoMetadata(userId), then UserPhoto_V2(id) Metadata, then binary image data

DirectReports_V2 requires the target manager's Microsoft Entra directory ID or user principal name (UPN). For the signed-in user, call MyProfile_V2('id,userPrincipalName'), then pass the returned id or fall back to userPrincipalName. For a known person, pass their directory ID or UPN directly.

Profile and direct-report pattern

Request only the fields the UI needs:

const fields = [
  'id',
  'displayName',
  'jobTitle',
  'department',
  'mail',
  'userPrincipalName',
  'officeLocation',
].join(',')

const myProfile = await Office365UsersService.MyProfile_V2(
  'id,userPrincipalName',
)
if (!myProfile.success) {
  throw new Error(myProfile.error?.message ?? 'Failed to resolve current user')
}

const managerId =
  myProfile.data?.id ?? myProfile.data?.userPrincipalName

if (!managerId) {
  throw new Error('Current user profile did not include an ID or UPN')
}

const result = await Office365UsersService.DirectReports_V2(
  managerId,
  fields,
)

if (!result.success) {
  throw new Error(result.error?.message ?? 'Failed to load direct reports')
}

const people = result.data?.value ?? []

An empty value array is valid and means the user has no direct reports.

Profile photos: binary response and CSP-safe rendering

Call UserPhotoMetadata before downloading the photo. HasPhoto: false is a valid no-photo state; render initials or another accessible fallback.

Image responses are a special runtime contract: the generated UserPhoto_V2 signature may declare IOperationResult<string>, but the Managed Apps SDK decodes an image/* response into a Uint8Array. Do not interpolate that byte array directly into a base64 URL, and do not use a blob: URL—the deployed App Player content security policy may block blob: under img-src.

Convert the bytes to a data: URL:

function bytesToBase64(bytes: Uint8Array) {
  const chunkSize = 0x8000
  let binary = ''

  for (let offset = 0; offset < bytes.length; offset += chunkSize) {
    binary += String.fromCharCode(
      ...bytes.subarray(offset, Math.min(offset + chunkSize, bytes.length)),
    )
  }

  return btoa(binary)
}

function toImageDataUrl(value: unknown, contentType = 'image/jpeg') {
  if (value instanceof Uint8Array) {
    return `data:${contentType};base64,${bytesToBase64(value)}`
  }

  if (value instanceof ArrayBuffer) {
    return `data:${contentType};base64,${bytesToBase64(new Uint8Array(value))}`
  }

  if (typeof value === 'string') {
    if (/^data:image\/[a-z0-9.+-]+;base64,/i.test(value)) {
      return value
    }

    if (/^https?:/i.test(value)) {
      throw new Error(
        'Profile photo returned an HTTP URL instead of binary image data.',
      )
    }

    return `data:${contentType};base64,${value}`
  }

  throw new Error('The profile photo response used an unsupported format.')
}

UserPhoto_V2 has a binary response contract and does not return an external image URL. An HTTP value indicates an unexpected response shape, so the helper rejects it rather than passing a potentially CSP-blocked or authenticated URL to <img src>.

Complete photo flow:

const metadata = await Office365UsersService.UserPhotoMetadata(userId)
if (!metadata.success) {
  throw new Error(metadata.error?.message ?? 'Failed to check profile photo')
}

if (!metadata.data?.HasPhoto) {
  return undefined
}

const photo = await Office365UsersService.UserPhoto_V2(userId)
if (!photo.success) {
  throw new Error(photo.error?.message ?? 'Failed to load profile photo')
}

return photo.data
  ? toImageDataUrl(photo.data, metadata.data.ContentType)
  : undefined

Always fetch photos through the generated connector service. Do not call Microsoft Graph directly and do not assign the authenticated connector runtime URL to <img src>.