@squareetlabs/capacitor-openwith v1.0.52
@squareetlabs/capacitor-openwith
Capacitor plugin to handle files and content shared from other apps on Android and iOS.
Installation
npm install @squareetlabs/capacitor-openwith
npx cap sync
Android Configuration
Add the following permissions to your android/app/src/main/AndroidManifest.xml
:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
iOS Configuration
Para permitir que tu aplicación reciba archivos compartidos desde otras aplicaciones, necesitas configurar una Share Extension:
1. Crear Share Extension
- Abre tu proyecto en XCode
- Ve a
File > New > Target
- Selecciona "Share Extension" en la sección de iOS
- Dale un nombre (por ejemplo, "ShareExtension")
- Asegúrate de que "Embed in Application" esté seleccionado
2. Configuración de App Groups
- Selecciona tu proyecto en XCode
- Selecciona el target principal de tu aplicación
- Ve a "Signing & Capabilities"
- Haz clic en "+" y añade "App Groups"
- Crea un nuevo grupo con el formato:
group.[tu-bundle-identifier]
- Repite el proceso para el target de la Share Extension
3. Configuración del Info.plist de la Share Extension
Añade lo siguiente a tu Info.plist de la Share Extension:
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>NSExtensionActivationRule</key>
<dict>
<key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
<integer>1</integer>
<key>NSExtensionActivationSupportsFileWithMaxCount</key>
<integer>1</integer>
<key>NSExtensionActivationSupportsText</key>
<true/>
<key>NSExtensionActivationSupportsImageWithMaxCount</key>
<integer>1</integer>
<key>NSExtensionActivationSupportsMovieWithMaxCount</key>
<integer>1</integer>
</dict>
</dict>
<key>NSExtensionMainStoryboard</key>
<string>MainInterface</string>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.share-services</string>
</dict>
4. Implementación del ShareViewController
Reemplaza el contenido del archivo ShareViewController.swift
con el siguiente código:
import UIKit
import Social
import MobileCoreServices
import UniformTypeIdentifiers
class ShareViewController: SLComposeServiceViewController {
override func isContentValid() -> Bool {
return true
}
override func didSelectPost() {
if let extensionItem = extensionContext?.inputItems.first as? NSExtensionItem,
let attachments = extensionItem.attachments {
for attachment in attachments {
if #available(iOS 14.0, *) {
if attachment.hasItemConformingToTypeIdentifier(UTType.url.identifier) {
attachment.loadItem(forTypeIdentifier: UTType.url.identifier, options: nil) { [weak self] (item, error) in
guard let self = self else { return }
if let url = item as? URL {
self.saveSharedContent(content: url.absoluteString, type: "url")
}
self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil)
}
}
} else {
// Para iOS 13 y anteriores
let urlIdentifier = kUTTypeURL as String
if attachment.hasItemConformingToTypeIdentifier(urlIdentifier) {
attachment.loadItem(forTypeIdentifier: urlIdentifier, options: nil) { [weak self] (item, error) in
guard let self = self else { return }
if let url = item as? URL {
self.saveSharedContent(content: url.absoluteString, type: "url")
}
self.extensionContext?.completeRequest(returningItems: nil, completionHandler: nil)
}
}
}
}
} else {
extensionContext?.completeRequest(returningItems: nil, completionHandler: nil)
}
}
override func configurationItems() -> [Any]! {
return []
}
private func saveSharedContent(content: String, type: String) {
// Usa el mismo App Group ID que en tu plugin
let bundleId = Bundle.main.bundleIdentifier?.split(separator: ".").dropLast().joined(separator: ".") ?? ""
let appGroupId = "group.\(bundleId)"
NSLog("OpenWith ShareExtension: Intentando guardar con App Group: \(appGroupId)")
if let userDefaults = UserDefaults(suiteName: appGroupId) {
userDefaults.set(content, forKey: "SharedContent")
userDefaults.set(type, forKey: "SharedContentType")
userDefaults.synchronize()
// Enviar notificación
DispatchQueue.main.async {
NotificationCenter.default.post(name: NSNotification.Name("SharedFile"), object: nil)
}
NSLog("OpenWith ShareExtension: Contenido guardado correctamente")
} else {
NSLog("OpenWith ShareExtension: No se pudo acceder a UserDefaults con App Group: \(appGroupId)")
}
}
}
5. Configuración del Bundle Identifier
Asegúrate de que el Bundle Identifier de la Share Extension esté correctamente configurado en el Info.plist:
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER).share-extension</string>
6. Configuración adicional en el Info.plist de la aplicación principal
Para que tu aplicación aparezca en las opciones de compartir, debes añadir lo siguiente al Info.plist de tu aplicación principal:
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<key>UIFileSharingEnabled</key>
<true/>
<key>UTImportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
<string>public.content</string>
</array>
<key>UTTypeDescription</key>
<string>Contenido compartido</string>
<key>UTTypeIdentifier</key>
<string>com.tu-empresa.app.shared-content</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>*</string>
</array>
<key>public.mime-type</key>
<array>
<string>*/*</string>
</array>
</dict>
</dict>
</array>
Asegúrate de reemplazar com.tu-empresa.app.shared-content
con un identificador único basado en el bundle identifier de tu aplicación.
7. Uso en tu aplicación
Una vez configurado, puedes escuchar los eventos de archivos compartidos en tu aplicación:
import { OpenWith } from '@capacitor-community/open-with';
OpenWith.addListener('receivedFiles', async (data) => {
console.log('Archivo recibido:', data);
// data.data contiene:
// - uri: URL del archivo
// - type: tipo MIME
// - source: información de la app que compartió (si está disponible)
// - extras: información adicional como título, texto, etc.
});
// Inicializar el plugin
await OpenWith.initialize();
8. Solución de Problemas
Si la Share Extension no aparece en el menú compartir:
- Verifica que el App Group esté correctamente configurado en ambos targets
- Asegúrate de que los tipos de archivo que quieres compartir estén incluidos en NSExtensionActivationRule
- Comprueba que la Share Extension esté firmada con el mismo equipo de desarrollo que la aplicación principal
- Limpia y reconstruye el proyecto (Product > Clean Build Folder)
- Desinstala y vuelve a instalar la aplicación en el dispositivo/simulador
API
The plugin provides the following functions:
addHandler()
: Adds a handler for shared file eventsinitialize()
: Initializes the pluginsetVerbosity({ level: number })
: Configures logging level (0: disabled, 1: enabled)
Interfaces
interface SharedData {
// Source application information
source?: {
packageName: string;
applicationName: string;
applicationIcon: string;
};
// Intent action
action?: string;
// MIME type
type?: string;
// Content URI
uri?: string;
// URI scheme
scheme?: string;
// Additional data
extras?: {
text?: string;
htmlText?: string;
subject?: string;
title?: string;
email?: string[];
cc?: string[];
bcc?: string[];
phoneNumber?: string;
latitude?: number;
longitude?: number;
eventTitle?: string;
eventDescription?: string;
eventLocation?: string;
[key: string]: any;
};
// ClipData information
clipData?: {
text?: string;
uri?: string;
htmlText?: string;
}[];
}
interface SharedFilesEvent {
data: SharedData;
}
Usage
The plugin can be used with various frameworks. Choose your framework below for specific implementation details:
1. Create a service to handle the plugin
// open-with.service.ts
import {Injectable} from '@angular/core';
import {Subject} from 'rxjs';
import {Capacitor} from '@capacitor/core';
import {OpenWith, SharedData, SharedFilesEvent} from '@squareetlabs/capacitor-openwith';
@Injectable({
providedIn: 'root'
})
export class OpenWithService {
private filesReceived = new Subject<SharedData>();
public filesReceived$ = this.filesReceived.asObservable();
constructor() {
this.init();
}
private async init() {
if (!Capacitor.isNativePlatform()) {
console.log('OpenWith only works on native platforms');
return;
}
try {
await OpenWith.addHandler(() => {
console.log('OpenWith handler added successfully');
});
await OpenWith.setVerbosity({level: 1});
await OpenWith.initialize();
await OpenWith.addListener('receivedFiles', (shared: SharedFilesEvent) => {
console.log('Received data:', shared);
if (shared && shared.data) {
this.filesReceived.next(shared.data);
}
});
} catch (error) {
console.error('Error initializing OpenWith:', error);
}
}
}
2. Use the service in your component
// my-component.ts
import {Component} from '@angular/core';
import {OpenWithService} from './open-with.service';
import {SharedData} from '@squareetlabs/capacitor-openwith';
@Component({
selector: 'app-my-component',
template: `
<div *ngIf="sharedData">
<h2>Shared Content</h2>
<p>From: {{sharedData.source?.applicationName}}</p>
<p>Type: {{sharedData.type}}</p>
<p>Text: {{sharedData.extras?.text}}</p>
<!-- Add more fields as needed -->
</div>
`
})
export class MyComponent {
sharedData: SharedData | null = null;
constructor(private openWithService: OpenWithService) {
this.openWithService.filesReceived$.subscribe(
(data: SharedData) => {
this.sharedData = data;
this.handleSharedContent(data);
}
);
}
private handleSharedContent(data: SharedData) {
// Example of how to handle different content types
if (data.extras?.text) {
console.log('Shared text:', data.extras.text);
}
if (data.extras?.phoneNumber) {
console.log('Phone number:', data.extras.phoneNumber);
}
if (data.uri) {
console.log('Shared URI:', data.uri);
}
// etc...
}
}
Framework Examples
React Example
// OpenWithProvider.tsx
import React, { createContext, useContext, useEffect, useState } from 'react';
import { Capacitor } from '@capacitor/core';
import { OpenWith, SharedData } from '@squareetlabs/capacitor-openwith';
interface OpenWithContextType {
sharedData: SharedData | null;
}
const OpenWithContext = createContext<OpenWithContextType>({ sharedData: null });
export const useOpenWith = () => useContext(OpenWithContext);
export const OpenWithProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [sharedData, setSharedData] = useState<SharedData | null>(null);
useEffect(() => {
if (!Capacitor.isNativePlatform()) {
console.log('OpenWith only works on native platforms');
return;
}
const initializeOpenWith = async () => {
try {
await OpenWith.addHandler(() => {
console.log('OpenWith handler added successfully');
});
await OpenWith.setVerbosity({ level: 1 });
await OpenWith.initialize();
await OpenWith.addListener('receivedFiles', (shared) => {
console.log('Received data:', shared);
if (shared && shared.data) {
setSharedData(shared.data);
}
});
} catch (error) {
console.error('Error initializing OpenWith:', error);
}
};
initializeOpenWith();
}, []);
return (
<OpenWithContext.Provider value={{ sharedData }}>
{children}
</OpenWithContext.Provider>
);
};
// App.tsx
import { OpenWithProvider } from './OpenWithProvider';
const App: React.FC = () => {
return (
<OpenWithProvider>
<YourApp />
</OpenWithProvider>
);
};
// SharedContent.tsx
import { useOpenWith } from './OpenWithProvider';
const SharedContent: React.FC = () => {
const { sharedData } = useOpenWith();
if (!sharedData) return null;
return (
<div>
<h2>Shared Content</h2>
<p>From: {sharedData.source?.applicationName}</p>
<p>Type: {sharedData.type}</p>
<p>Text: {sharedData.extras?.text}</p>
</div>
);
};
Vue Example
// openWithPlugin.ts
import { ref, readonly } from 'vue';
import { Capacitor } from '@capacitor/core';
import { OpenWith, SharedData } from '@squareetlabs/capacitor-openwith';
const sharedData = ref<SharedData | null>(null);
export const useOpenWith = () => {
const initialize = async () => {
if (!Capacitor.isNativePlatform()) {
console.log('OpenWith only works on native platforms');
return;
}
try {
await OpenWith.addHandler(() => {
console.log('OpenWith handler added successfully');
});
await OpenWith.setVerbosity({ level: 1 });
await OpenWith.initialize();
await OpenWith.addListener('receivedFiles', (shared) => {
console.log('Received data:', shared);
if (shared && shared.data) {
sharedData.value = shared.data;
}
});
} catch (error) {
console.error('Error initializing OpenWith:', error);
}
};
return {
sharedData: readonly(sharedData),
initialize
};
};
// main.ts
import { createApp } from 'vue';
import App from './App.vue';
const app = createApp(App);
app.mount('#app');
// App.vue
<script setup lang="ts">
import { onMounted } from 'vue';
import { useOpenWith } from './openWithPlugin';
const { sharedData, initialize } = useOpenWith();
onMounted(() => {
initialize();
});
</script>
<template>
<div v-if="sharedData">
<h2>Shared Content</h2>
<p>From: {{ sharedData.source?.applicationName }}</p>
<p>Type: {{ sharedData.type }}</p>
<p>Text: {{ sharedData.extras?.text }}</p>
</div>
</template>
Svelte Example (Bonus)
// openWith.ts
import { writable } from 'svelte/store';
import { Capacitor } from '@capacitor/core';
import { OpenWith, SharedData } from '@squareetlabs/capacitor-openwith';
export const sharedData = writable<SharedData | null>(null);
export const initializeOpenWith = async () => {
if (!Capacitor.isNativePlatform()) {
console.log('OpenWith only works on native platforms');
return;
}
try {
await OpenWith.addHandler(() => {
console.log('OpenWith handler added successfully');
});
await OpenWith.setVerbosity({ level: 1 });
await OpenWith.initialize();
await OpenWith.addListener('receivedFiles', (shared) => {
console.log('Received data:', shared);
if (shared && shared.data) {
sharedData.set(shared.data);
}
});
} catch (error) {
console.error('Error initializing OpenWith:', error);
}
};
// App.svelte
<script lang="ts">
import { onMount } from 'svelte';
import { sharedData, initializeOpenWith } from './openWith';
onMount(() => {
initializeOpenWith();
});
</script>
{#if $sharedData}
<div>
<h2>Shared Content</h2>
<p>From: {$sharedData.source?.applicationName}</p>
<p>Type: {$sharedData.type}</p>
<p>Text: {$sharedData.extras?.text}</p>
</div>
{/if}
## Supported Content Types
The plugin can handle various types of shared content:
- Plain text
- URLs
- Images
- Documents
- Contacts
- Locations
- Calendar events
- Phone numbers
- Emails
- Media content
- And more...
## Debugging
To enable detailed logging, use:
```typescript
await OpenWith.setVerbosity({level: 1});
Supported Platforms
- ✅ Android
- ✅ iOS (coming soon)
License
MIT
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago